diff mbox series

[net-next,v2,1/3] dsa: Implement RMU layer in DSA

Message ID 20220826063816.948397-2-mattias.forsblad@gmail.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series net: dsa: mv88e6xxx: Add RMU support | expand

Checks

Context Check Description
netdev/tree_selection success Clearly marked for net-next
netdev/fixes_present success Fixes tag not required for -next series
netdev/subject_prefix success Link
netdev/cover_letter success Series has a cover letter
netdev/patch_count success Link
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 5436 this patch: 5436
netdev/cc_maintainers success CCed 9 of 9 maintainers
netdev/build_clang success Errors and warnings before: 1137 this patch: 1137
netdev/module_param success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 5593 this patch: 5593
netdev/checkpatch warning CHECK: Alignment should match open parenthesis WARNING: line length of 84 exceeds 80 columns WARNING: line length of 85 exceeds 80 columns WARNING: line length of 99 exceeds 80 columns
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Mattias Forsblad Aug. 26, 2022, 6:38 a.m. UTC
Support handling of layer 2 part for RMU frames which is
handled in-band with other DSA traffic.

Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
---
 include/net/dsa.h             |   7 +++
 include/uapi/linux/if_ether.h |   1 +
 net/dsa/tag_dsa.c             | 109 +++++++++++++++++++++++++++++++++-
 3 files changed, 114 insertions(+), 3 deletions(-)

Comments

Andrew Lunn Aug. 26, 2022, 7:27 p.m. UTC | #1
On Fri, Aug 26, 2022 at 08:38:14AM +0200, Mattias Forsblad wrote:
> Support handling of layer 2 part for RMU frames which is
> handled in-band with other DSA traffic.
> 
> Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
> ---
>  include/net/dsa.h             |   7 +++
>  include/uapi/linux/if_ether.h |   1 +
>  net/dsa/tag_dsa.c             | 109 +++++++++++++++++++++++++++++++++-
>  3 files changed, 114 insertions(+), 3 deletions(-)
> 
> diff --git a/include/net/dsa.h b/include/net/dsa.h
> index f2ce12860546..54f7f3494f84 100644
> --- a/include/net/dsa.h
> +++ b/include/net/dsa.h
> @@ -92,6 +92,7 @@ struct dsa_switch;
>  struct dsa_device_ops {
>  	struct sk_buff *(*xmit)(struct sk_buff *skb, struct net_device *dev);
>  	struct sk_buff *(*rcv)(struct sk_buff *skb, struct net_device *dev);
> +	int (*inband_xmit)(struct sk_buff *skb, struct net_device *dev, int seq_no);
>  	void (*flow_dissect)(const struct sk_buff *skb, __be16 *proto,
>  			     int *offset);
>  	int (*connect)(struct dsa_switch *ds);
> @@ -1193,6 +1194,12 @@ struct dsa_switch_ops {
>  	void	(*master_state_change)(struct dsa_switch *ds,
>  				       const struct net_device *master,
>  				       bool operational);
> +
> +	/*
> +	 * RMU operations
> +	 */
> +	int (*inband_receive)(struct dsa_switch *ds, struct sk_buff *skb,
> +			int seq_no);
>  };

Hi Mattias

Vladimer pointed you towards the qca driver, in a comment for your
RFC. qca already has support for switch commands via Ethernet frames.

The point he was trying to make is that you should look at that
code. The concept of executing a command via an Ethernet frame, and
expecting a reply via an Ethernet frame is generic. The format of
those frames is specific to the switch. We want the generic parts to
look the same for all switches. If possible, we want to implement it
once in the dsa core, so all switch drivers share it. Less code,
better tested code, less bugs, easier maintenance.

Take a look at qca_tagger_data. Please use the same mechanism with
mv88e6xxx. But also look deeper. What else can be shared? You need a
buffer to put the request in, you need to send it, you need to wait
for the reply, you need to pass the results to the driver, you need to
free that buffer afterwards. That should all be common. Look at these
parts in the qca driver. Can you make them generic, move them into the
DSA core? Are there other parts which could be shared?

    Andrew
Mattias Forsblad Aug. 29, 2022, 6:10 a.m. UTC | #2
On 2022-08-26 21:27, Andrew Lunn wrote:
> On Fri, Aug 26, 2022 at 08:38:14AM +0200, Mattias Forsblad wrote:
>> Support handling of layer 2 part for RMU frames which is
>> handled in-band with other DSA traffic.
>>
>> Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
>> ---
>>  include/net/dsa.h             |   7 +++
>>  include/uapi/linux/if_ether.h |   1 +
>>  net/dsa/tag_dsa.c             | 109 +++++++++++++++++++++++++++++++++-
>>  3 files changed, 114 insertions(+), 3 deletions(-)
>>
>> diff --git a/include/net/dsa.h b/include/net/dsa.h
>> index f2ce12860546..54f7f3494f84 100644
>> --- a/include/net/dsa.h
>> +++ b/include/net/dsa.h
>> @@ -92,6 +92,7 @@ struct dsa_switch;
>>  struct dsa_device_ops {
>>  	struct sk_buff *(*xmit)(struct sk_buff *skb, struct net_device *dev);
>>  	struct sk_buff *(*rcv)(struct sk_buff *skb, struct net_device *dev);
>> +	int (*inband_xmit)(struct sk_buff *skb, struct net_device *dev, int seq_no);
>>  	void (*flow_dissect)(const struct sk_buff *skb, __be16 *proto,
>>  			     int *offset);
>>  	int (*connect)(struct dsa_switch *ds);
>> @@ -1193,6 +1194,12 @@ struct dsa_switch_ops {
>>  	void	(*master_state_change)(struct dsa_switch *ds,
>>  				       const struct net_device *master,
>>  				       bool operational);
>> +
>> +	/*
>> +	 * RMU operations
>> +	 */
>> +	int (*inband_receive)(struct dsa_switch *ds, struct sk_buff *skb,
>> +			int seq_no);
>>  };
> 
> Hi Mattias
> 
> Vladimer pointed you towards the qca driver, in a comment for your
> RFC. qca already has support for switch commands via Ethernet frames.
> 
> The point he was trying to make is that you should look at that
> code. The concept of executing a command via an Ethernet frame, and
> expecting a reply via an Ethernet frame is generic. The format of
> those frames is specific to the switch. We want the generic parts to
> look the same for all switches. If possible, we want to implement it
> once in the dsa core, so all switch drivers share it. Less code,
> better tested code, less bugs, easier maintenance.
> 
> Take a look at qca_tagger_data. Please use the same mechanism with

This I can do which makes sense.

> mv88e6xxx. But also look deeper. What else can be shared? You need a

I can also make a generic dsa_eth_tx_timeout routine which
handles the sending and receiving of cmd frames.

> buffer to put the request in, you need to send it, you need to wait

The skb is the buffer and it's up to the driver to decode it properly?
I've looked into the qca driver and that uses a small static buffer
for replies, no buffer lifetime cycle.

> for the reply, you need to pass the results to the driver, you need to
> free that buffer afterwards. That should all be common. Look at these
> parts in the qca driver. Can you make them generic, move them into the
> DSA core? Are there other parts which could be shared?

I cannot change the qca code as I have no way of verifying that the
resulting code works.

> 
>     Andrew
> 
> 

/Mattias
Andrew Lunn Aug. 29, 2022, 12:42 p.m. UTC | #3
> > Hi Mattias
> > 
> > Vladimer pointed you towards the qca driver, in a comment for your
> > RFC. qca already has support for switch commands via Ethernet frames.
> > 
> > The point he was trying to make is that you should look at that
> > code. The concept of executing a command via an Ethernet frame, and
> > expecting a reply via an Ethernet frame is generic. The format of
> > those frames is specific to the switch. We want the generic parts to
> > look the same for all switches. If possible, we want to implement it
> > once in the dsa core, so all switch drivers share it. Less code,
> > better tested code, less bugs, easier maintenance.
> > 
> > Take a look at qca_tagger_data. Please use the same mechanism with
> 
> This I can do which makes sense.
> 
> > mv88e6xxx. But also look deeper. What else can be shared? You need a
> 
> I can also make a generic dsa_eth_tx_timeout routine which
> handles the sending and receiving of cmd frames.
> 
> > buffer to put the request in, you need to send it, you need to wait
> 
> The skb is the buffer and it's up to the driver to decode it properly?

Yes, the tagger layer passes the skb, which contains the complete
Ethernet frame, plus additional meta data. But you need to watch out
for the life cycle of that skb:

        /* Ethernet mgmt read/write packet */
        if (pk_type == QCA_HDR_RECV_TYPE_RW_REG_ACK) {
                if (likely(tagger_data->rw_reg_ack_handler))
                        tagger_data->rw_reg_ack_handler(ds, skb);
                return NULL;
        }

returning NULL means the DSA core will free the skb when the call to
qca_tag_rcv() returns. So either you need to take a copy of the data,
clone the skb, or change this code somehow. See what makes most sense
for generic code.


> I've looked into the qca driver and that uses a small static buffer
> for replies, no buffer lifetime cycle.
> 
> > for the reply, you need to pass the results to the driver, you need to
> > free that buffer afterwards. That should all be common. Look at these
> > parts in the qca driver. Can you make them generic, move them into the
> > DSA core? Are there other parts which could be shared?
> 
> I cannot change the qca code as I have no way of verifying that the
> resulting code works.

You can change it. You can at least compile test your change. The QCA
developers are also quite active, and so will probably help out
testing whatever you change. And ideally, you want lots of simple,
obviously correct changes, which i can review and say look correct.

Changing code which you cannot test is very normal in Linux, do your
best, and ask for testing.

      Andrew
Vladimir Oltean Aug. 30, 2022, 3:47 p.m. UTC | #4
On Fri, Aug 26, 2022 at 08:38:14AM +0200, Mattias Forsblad wrote:
> Support handling of layer 2 part for RMU frames which is
> handled in-band with other DSA traffic.

Great explanation, everything is clear!

> 
> Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
> ---
>  include/net/dsa.h             |   7 +++
>  include/uapi/linux/if_ether.h |   1 +
>  net/dsa/tag_dsa.c             | 109 +++++++++++++++++++++++++++++++++-
>  3 files changed, 114 insertions(+), 3 deletions(-)
> 
> diff --git a/include/net/dsa.h b/include/net/dsa.h
> index f2ce12860546..54f7f3494f84 100644
> --- a/include/net/dsa.h
> +++ b/include/net/dsa.h
> @@ -92,6 +92,7 @@ struct dsa_switch;
>  struct dsa_device_ops {
>  	struct sk_buff *(*xmit)(struct sk_buff *skb, struct net_device *dev);
>  	struct sk_buff *(*rcv)(struct sk_buff *skb, struct net_device *dev);
> +	int (*inband_xmit)(struct sk_buff *skb, struct net_device *dev, int seq_no);

There isn't a reason that I can see to expand the DSA tagger ops with an
inband_xmit(). DSA tagging protocol ops are reactive hooks to modify
packets belonging to slave interfaces, rather than initiators of traffic.

Here you are calling tag_ops->inband_xmit() from mv88e6xxx_rmu_tx(),
i.e. this operation is never invoked from the DSA core, but from a code
path fully in control of the hardware driver. We don't usually (ever?)
use DSA ops in this way, but rather just a way for the framework to
invoke driver-specific code.

Is there a reason why dsa_inband_xmit_ll() cannot simply live within the
mv88e6xxx driver (the direct caller) rather than within the dsa/edsa tagger?

Tagging protocols can be changed at driver runtime, but only while the
DSA master is down. So when the master goes up, you can also check which
tagging protocol is in use, and cache/use that to construct the skb.

Furthermore, there is no slave interface associated with RMU traffic,
although in your proposed implementation here, there is (that's what
"struct net_device *dev" passed here is).

Which slave @dev are you passing? That's right, dev_get_by_name(&init_net, "chan0").
I think it's pretty obvious there is a systematic problem with your approach.
Not everyone has a slave net device called chan0 in the main netns.

The qca8k implementation, as opposed to yours, calls dev_queue_xmit()
with skb->dev directly on the DSA master, and forgoes the DSA tagger on
TX. I don't see a problem with that approach, on the contrary, I think
it's better and simpler.

>  	void (*flow_dissect)(const struct sk_buff *skb, __be16 *proto,
>  			     int *offset);
>  	int (*connect)(struct dsa_switch *ds);
> @@ -1193,6 +1194,12 @@ struct dsa_switch_ops {
>  	void	(*master_state_change)(struct dsa_switch *ds,
>  				       const struct net_device *master,
>  				       bool operational);
> +
> +	/*
> +	 * RMU operations
> +	 */
> +	int (*inband_receive)(struct dsa_switch *ds, struct sk_buff *skb,
> +			int seq_no);
>  };
>  
>  #define DSA_DEVLINK_PARAM_DRIVER(_id, _name, _type, _cmodes)		\
> diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h
> index d370165bc621..9de1bdc7cccc 100644
> --- a/include/uapi/linux/if_ether.h
> +++ b/include/uapi/linux/if_ether.h
> @@ -158,6 +158,7 @@
>  #define ETH_P_MCTP	0x00FA		/* Management component transport
>  					 * protocol packets
>  					 */
> +#define ETH_P_RMU_DSA   0x00FB          /* RMU DSA protocol */

I think it's more normal to set skb->protocol = eth->h_proto = htons(the actual EtherType),
rather than introducing a new skb->protocol which won't be used anywhere.

>  
>  /*
>   *	This is an Ethernet frame header.
> diff --git a/net/dsa/tag_dsa.c b/net/dsa/tag_dsa.c
> index e4b6e3f2a3db..36f02e7dd3c3 100644
> --- a/net/dsa/tag_dsa.c
> +++ b/net/dsa/tag_dsa.c
> @@ -123,6 +123,90 @@ enum dsa_code {
>  	DSA_CODE_RESERVED_7    = 7
>  };
>  
> +#define DSA_RMU_RESV1   0x3e
> +#define DSA_RMU         1
> +#define DSA_RMU_PRIO    6
> +#define DSA_RMU_RESV2   0xf
> +
> +static int dsa_inband_xmit_ll(struct sk_buff *skb, struct net_device *dev,
> +			      const u8 *header, int header_len, int seq_no)
> +{
> +	static const u8 dest_addr[ETH_ALEN] = { 0x01, 0x50, 0x43, 0x00, 0x00, 0x00 };
> +	struct dsa_port *dp;
> +	struct ethhdr *eth;
> +	u8 *data;
> +
> +	if (!dev)
> +		return -ENODEV;
> +
> +	dp = dsa_slave_to_port(dev);
> +	if (!dp)
> +		return -ENODEV;
> +
> +	/* Create RMU L2 header */
> +	data = skb_push(skb, 6);
> +	data[0] = (DSA_CMD_FROM_CPU << 6) | dp->ds->index;
> +	data[1] = DSA_RMU_RESV1 << 2 | DSA_RMU << 1;
> +	data[2] = DSA_RMU_PRIO << 5 | DSA_RMU_RESV2;
> +	data[3] = seq_no;
> +	data[4] = 0;
> +	data[5] = 0;
> +
> +	/* Add header if any */
> +	if (header) {
> +		data = skb_push(skb, header_len);
> +		memcpy(data, header, header_len);
> +	}
> +
> +	/* Create MAC header */
> +	eth = (struct ethhdr *)skb_push(skb, 2 * ETH_ALEN);
> +	memcpy(eth->h_source, dev->dev_addr, ETH_ALEN);
> +	memcpy(eth->h_dest, dest_addr, ETH_ALEN);
> +
> +	skb->protocol = htons(ETH_P_RMU_DSA);
> +
> +	dev_queue_xmit(skb);

Just for things to be 100% clear for everyone. Per your design, we have
dsa_inband_xmit() which gets called by the driver with a slave @dev, and
this constructs an skb without the DSA/EDSA header. Then dev_queue_xmit()
will invoke the ndo_start_xmit of DSA, dsa_slave_xmit(). In turn this
will enter the tagging protocol driver a second time, through p->xmit() ->
dsa_xmit_ll(). The second time is when the DSA/EDSA header is actually
introduced.

This is way more complicated than it needs to be.

> +
> +	return 0;
> +}
> +
> +static int dsa_inband_rcv_ll(struct sk_buff *skb, struct net_device *dev)
> +{
> +	struct dsa_switch *ds;
> +	int source_device;
> +	u8 *dsa_header;
> +	int rcv_seqno;
> +	int ret = 0;
> +
> +	if (!dev || !dev->dsa_ptr)
> +		return 0;

Way too defensive programming which wastes CPU cycles for nothing. The
DSA rcv hook runs as an ETH_P_XDSA packet_type handler on the DSA master,
based on eth_type_trans() which had set skb->protocol to ETH_P_XDSA
based on the presence of dev->dsa_ptr. So yes, the DSA master is not NULL,
and yes, the DSA master's dev->dsa_ptr is not NULL either.

> +
> +	ds = dev->dsa_ptr->ds;
> +	if (!ds)
> +		return 0;

We don't have NULL pointers in cpu_dp->ds lying around. dp->ds is set by
dsa_port_touch(), which runs earlier than dsa_master_setup() sets
dev->dsa_ptr in such a way that we start processing RX packets.

> +
> +	dsa_header = skb->data - 2;

Please use dsa_etype_header_pos_rx(). In fact, this pointer was already
available in dsa_rcv_ll().

> +
> +	source_device = dsa_header[0] & 0x1f;
> +	ds = dsa_switch_find(ds->dst->index, source_device);
> +	if (!ds) {
> +		net_dbg_ratelimited("DSA inband: Didn't find switch with index %d", source_device);
> +		return -EINVAL;
> +	}
> +
> +	/* Get rcv seqno */
> +	rcv_seqno = dsa_header[3];
> +
> +	skb_pull(skb, DSA_HLEN);
> +
> +	if (ds->ops && ds->ops->inband_receive(ds, skb, rcv_seqno)) {

I think the reason why Andrew is asking you to find common aspects with
qca8k which can be further generalized is because your proposed code is
very ambitious, introducing a generic ds->ops->inband_receive() like this.

I personally wouldn't be so ambitious for myself. The way the qca8k
driver and tagging protocol work together is that they set up a group of
driver-specific function pointers, rw_reg_ack_handler and mib_autocast_handler,
through which the switch driver connected to the tagger may subscribe as
a consumer to the received Ethernet management packets. Whereas you are
proposing a one-size-fits-all ds->ops->inband_receive with no effort to
see if it fits even the single other user of this concept, qca8k.

What I would do is introduce one more case of driver-specific consumer
of RMU packets, specific to the dsa/edsa tagger <-> mv88e6xxx driver pair.
I'd let things sit for a while, maybe wait for a third user, then see
how/if the prototype for consuming Ethernet management packets can be
made generic.

But in general, we need drivers to handle non-data RX packets coming
from the switch for all sorts of reasons. For example SJA1110 delivers
back TX timestamps as Ethernet packets, and I wouldn't consider
expanding ds->ops for that. This driver-specific hook mechanism
("tagger owned storage" as I named it) is flexible enough to allow each
driver to respond to the needs of its hardware, without needing
everybody else to follow suit or suffer of ops bloat because of it.
I wouldn't rush.

> +		dev_dbg_ratelimited(ds->dev, "DSA inband: error decoding packet");
> +		ret = -EIO;
> +	}
> +
> +	return ret;
Mattias Forsblad Aug. 31, 2022, 5:55 a.m. UTC | #5
On 2022-08-30 17:47, Vladimir Oltean wrote:

>> Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
>> ---
>>  include/net/dsa.h             |   7 +++
>>  include/uapi/linux/if_ether.h |   1 +
>>  net/dsa/tag_dsa.c             | 109 +++++++++++++++++++++++++++++++++-
>>  3 files changed, 114 insertions(+), 3 deletions(-)
>>
>> diff --git a/include/net/dsa.h b/include/net/dsa.h
>> index f2ce12860546..54f7f3494f84 100644
>> --- a/include/net/dsa.h
>> +++ b/include/net/dsa.h
>> @@ -92,6 +92,7 @@ struct dsa_switch;
>>  struct dsa_device_ops {
>>  	struct sk_buff *(*xmit)(struct sk_buff *skb, struct net_device *dev);
>>  	struct sk_buff *(*rcv)(struct sk_buff *skb, struct net_device *dev);
>> +	int (*inband_xmit)(struct sk_buff *skb, struct net_device *dev, int seq_no);
> 
> There isn't a reason that I can see to expand the DSA tagger ops with an
> inband_xmit(). DSA tagging protocol ops are reactive hooks to modify
> packets belonging to slave interfaces, rather than initiators of traffic.
> 
> Here you are calling tag_ops->inband_xmit() from mv88e6xxx_rmu_tx(),
> i.e. this operation is never invoked from the DSA core, but from a code
> path fully in control of the hardware driver. We don't usually (ever?)
> use DSA ops in this way, but rather just a way for the framework to
> invoke driver-specific code.
> 
> Is there a reason why dsa_inband_xmit_ll() cannot simply live within the
> mv88e6xxx driver (the direct caller) rather than within the dsa/edsa tagger?
> 
> Tagging protocols can be changed at driver runtime, but only while the
> DSA master is down. So when the master goes up, you can also check which
> tagging protocol is in use, and cache/use that to construct the skb.
> 
> Furthermore, there is no slave interface associated with RMU traffic,
> although in your proposed implementation here, there is (that's what
> "struct net_device *dev" passed here is).
> 
> Which slave @dev are you passing? That's right, dev_get_by_name(&init_net, "chan0").
> I think it's pretty obvious there is a systematic problem with your approach.
> Not everyone has a slave net device called chan0 in the main netns.
> 
> The qca8k implementation, as opposed to yours, calls dev_queue_xmit()
> with skb->dev directly on the DSA master, and forgoes the DSA tagger on
> TX. I don't see a problem with that approach, on the contrary, I think
> it's better and simpler.
> 

Yes, I agree and will rework it more in line with qca8k.

>>  	void (*flow_dissect)(const struct sk_buff *skb, __be16 *proto,
>>  			     int *offset);
>>  	int (*connect)(struct dsa_switch *ds);
>> @@ -1193,6 +1194,12 @@ struct dsa_switch_ops {
>>  	void	(*master_state_change)(struct dsa_switch *ds,
>>  				       const struct net_device *master,
>>  				       bool operational);
>> +
>> +	/*
>> +	 * RMU operations
>> +	 */
>> +	int (*inband_receive)(struct dsa_switch *ds, struct sk_buff *skb,
>> +			int seq_no);
>>  };
>>  
>>  #define DSA_DEVLINK_PARAM_DRIVER(_id, _name, _type, _cmodes)		\
>> diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h
>> index d370165bc621..9de1bdc7cccc 100644
>> --- a/include/uapi/linux/if_ether.h
>> +++ b/include/uapi/linux/if_ether.h
>> @@ -158,6 +158,7 @@
>>  #define ETH_P_MCTP	0x00FA		/* Management component transport
>>  					 * protocol packets
>>  					 */
>> +#define ETH_P_RMU_DSA   0x00FB          /* RMU DSA protocol */
> 
> I think it's more normal to set skb->protocol = eth->h_proto = htons(the actual EtherType),
> rather than introducing a new skb->protocol which won't be used anywhere.
> 
>>  
>>  /*
>>   *	This is an Ethernet frame header.
>> diff --git a/net/dsa/tag_dsa.c b/net/dsa/tag_dsa.c
>> index e4b6e3f2a3db..36f02e7dd3c3 100644
>> --- a/net/dsa/tag_dsa.c
>> +++ b/net/dsa/tag_dsa.c
>> @@ -123,6 +123,90 @@ enum dsa_code {
>>  	DSA_CODE_RESERVED_7    = 7
>>  };
>>  
>> +#define DSA_RMU_RESV1   0x3e
>> +#define DSA_RMU         1
>> +#define DSA_RMU_PRIO    6
>> +#define DSA_RMU_RESV2   0xf
>> +
>> +static int dsa_inband_xmit_ll(struct sk_buff *skb, struct net_device *dev,
>> +			      const u8 *header, int header_len, int seq_no)
>> +{
>> +	static const u8 dest_addr[ETH_ALEN] = { 0x01, 0x50, 0x43, 0x00, 0x00, 0x00 };
>> +	struct dsa_port *dp;
>> +	struct ethhdr *eth;
>> +	u8 *data;
>> +
>> +	if (!dev)
>> +		return -ENODEV;
>> +
>> +	dp = dsa_slave_to_port(dev);
>> +	if (!dp)
>> +		return -ENODEV;
>> +
>> +	/* Create RMU L2 header */
>> +	data = skb_push(skb, 6);
>> +	data[0] = (DSA_CMD_FROM_CPU << 6) | dp->ds->index;
>> +	data[1] = DSA_RMU_RESV1 << 2 | DSA_RMU << 1;
>> +	data[2] = DSA_RMU_PRIO << 5 | DSA_RMU_RESV2;
>> +	data[3] = seq_no;
>> +	data[4] = 0;
>> +	data[5] = 0;
>> +
>> +	/* Add header if any */
>> +	if (header) {
>> +		data = skb_push(skb, header_len);
>> +		memcpy(data, header, header_len);
>> +	}
>> +
>> +	/* Create MAC header */
>> +	eth = (struct ethhdr *)skb_push(skb, 2 * ETH_ALEN);
>> +	memcpy(eth->h_source, dev->dev_addr, ETH_ALEN);
>> +	memcpy(eth->h_dest, dest_addr, ETH_ALEN);
>> +
>> +	skb->protocol = htons(ETH_P_RMU_DSA);
>> +
>> +	dev_queue_xmit(skb);
> 
> Just for things to be 100% clear for everyone. Per your design, we have
> dsa_inband_xmit() which gets called by the driver with a slave @dev, and
> this constructs an skb without the DSA/EDSA header. Then dev_queue_xmit()
> will invoke the ndo_start_xmit of DSA, dsa_slave_xmit(). In turn this
> will enter the tagging protocol driver a second time, through p->xmit() ->
> dsa_xmit_ll(). The second time is when the DSA/EDSA header is actually
> introduced.
> 
> This is way more complicated than it needs to be.
> 

See comment above.

>> +
>> +	return 0;
>> +}
>> +
>> +static int dsa_inband_rcv_ll(struct sk_buff *skb, struct net_device *dev)
>> +{
>> +	struct dsa_switch *ds;
>> +	int source_device;
>> +	u8 *dsa_header;
>> +	int rcv_seqno;
>> +	int ret = 0;
>> +
>> +	if (!dev || !dev->dsa_ptr)
>> +		return 0;
> 
> Way too defensive programming which wastes CPU cycles for nothing. The
> DSA rcv hook runs as an ETH_P_XDSA packet_type handler on the DSA master,
> based on eth_type_trans() which had set skb->protocol to ETH_P_XDSA
> based on the presence of dev->dsa_ptr. So yes, the DSA master is not NULL,
> and yes, the DSA master's dev->dsa_ptr is not NULL either.
> 
>> +
>> +	ds = dev->dsa_ptr->ds;
>> +	if (!ds)
>> +		return 0;
> 
> We don't have NULL pointers in cpu_dp->ds lying around. dp->ds is set by
> dsa_port_touch(), which runs earlier than dsa_master_setup() sets
> dev->dsa_ptr in such a way that we start processing RX packets.
> 
>> +
>> +	dsa_header = skb->data - 2;
> 
> Please use dsa_etype_header_pos_rx(). In fact, this pointer was already
> available in dsa_rcv_ll().
> 

I did see it, thanks.

>> +
>> +	source_device = dsa_header[0] & 0x1f;
>> +	ds = dsa_switch_find(ds->dst->index, source_device);
>> +	if (!ds) {
>> +		net_dbg_ratelimited("DSA inband: Didn't find switch with index %d", source_device);
>> +		return -EINVAL;
>> +	}
>> +
>> +	/* Get rcv seqno */
>> +	rcv_seqno = dsa_header[3];
>> +
>> +	skb_pull(skb, DSA_HLEN);
>> +
>> +	if (ds->ops && ds->ops->inband_receive(ds, skb, rcv_seqno)) {
> 
> I think the reason why Andrew is asking you to find common aspects with
> qca8k which can be further generalized is because your proposed code is
> very ambitious, introducing a generic ds->ops->inband_receive() like this.
> 
> I personally wouldn't be so ambitious for myself. The way the qca8k
> driver and tagging protocol work together is that they set up a group of
> driver-specific function pointers, rw_reg_ack_handler and mib_autocast_handler,
> through which the switch driver connected to the tagger may subscribe as
> a consumer to the received Ethernet management packets. Whereas you are
> proposing a one-size-fits-all ds->ops->inband_receive with no effort to
> see if it fits even the single other user of this concept, qca8k.
> 
> What I would do is introduce one more case of driver-specific consumer
> of RMU packets, specific to the dsa/edsa tagger <-> mv88e6xxx driver pair.
> I'd let things sit for a while, maybe wait for a third user, then see
> how/if the prototype for consuming Ethernet management packets can be
> made generic.
> 
> But in general, we need drivers to handle non-data RX packets coming
> from the switch for all sorts of reasons. For example SJA1110 delivers
> back TX timestamps as Ethernet packets, and I wouldn't consider
> expanding ds->ops for that. This driver-specific hook mechanism
> ("tagger owned storage" as I named it) is flexible enough to allow each
> driver to respond to the needs of its hardware, without needing
> everybody else to follow suit or suffer of ops bloat because of it.
> I wouldn't rush.
> 

I'll come back with a new version to discuss.

>> +		dev_dbg_ratelimited(ds->dev, "DSA inband: error decoding packet");
>> +		ret = -EIO;
>> +	}
>> +
>> +	return ret;
diff mbox series

Patch

diff --git a/include/net/dsa.h b/include/net/dsa.h
index f2ce12860546..54f7f3494f84 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -92,6 +92,7 @@  struct dsa_switch;
 struct dsa_device_ops {
 	struct sk_buff *(*xmit)(struct sk_buff *skb, struct net_device *dev);
 	struct sk_buff *(*rcv)(struct sk_buff *skb, struct net_device *dev);
+	int (*inband_xmit)(struct sk_buff *skb, struct net_device *dev, int seq_no);
 	void (*flow_dissect)(const struct sk_buff *skb, __be16 *proto,
 			     int *offset);
 	int (*connect)(struct dsa_switch *ds);
@@ -1193,6 +1194,12 @@  struct dsa_switch_ops {
 	void	(*master_state_change)(struct dsa_switch *ds,
 				       const struct net_device *master,
 				       bool operational);
+
+	/*
+	 * RMU operations
+	 */
+	int (*inband_receive)(struct dsa_switch *ds, struct sk_buff *skb,
+			int seq_no);
 };
 
 #define DSA_DEVLINK_PARAM_DRIVER(_id, _name, _type, _cmodes)		\
diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h
index d370165bc621..9de1bdc7cccc 100644
--- a/include/uapi/linux/if_ether.h
+++ b/include/uapi/linux/if_ether.h
@@ -158,6 +158,7 @@ 
 #define ETH_P_MCTP	0x00FA		/* Management component transport
 					 * protocol packets
 					 */
+#define ETH_P_RMU_DSA   0x00FB          /* RMU DSA protocol */
 
 /*
  *	This is an Ethernet frame header.
diff --git a/net/dsa/tag_dsa.c b/net/dsa/tag_dsa.c
index e4b6e3f2a3db..36f02e7dd3c3 100644
--- a/net/dsa/tag_dsa.c
+++ b/net/dsa/tag_dsa.c
@@ -123,6 +123,90 @@  enum dsa_code {
 	DSA_CODE_RESERVED_7    = 7
 };
 
+#define DSA_RMU_RESV1   0x3e
+#define DSA_RMU         1
+#define DSA_RMU_PRIO    6
+#define DSA_RMU_RESV2   0xf
+
+static int dsa_inband_xmit_ll(struct sk_buff *skb, struct net_device *dev,
+			      const u8 *header, int header_len, int seq_no)
+{
+	static const u8 dest_addr[ETH_ALEN] = { 0x01, 0x50, 0x43, 0x00, 0x00, 0x00 };
+	struct dsa_port *dp;
+	struct ethhdr *eth;
+	u8 *data;
+
+	if (!dev)
+		return -ENODEV;
+
+	dp = dsa_slave_to_port(dev);
+	if (!dp)
+		return -ENODEV;
+
+	/* Create RMU L2 header */
+	data = skb_push(skb, 6);
+	data[0] = (DSA_CMD_FROM_CPU << 6) | dp->ds->index;
+	data[1] = DSA_RMU_RESV1 << 2 | DSA_RMU << 1;
+	data[2] = DSA_RMU_PRIO << 5 | DSA_RMU_RESV2;
+	data[3] = seq_no;
+	data[4] = 0;
+	data[5] = 0;
+
+	/* Add header if any */
+	if (header) {
+		data = skb_push(skb, header_len);
+		memcpy(data, header, header_len);
+	}
+
+	/* Create MAC header */
+	eth = (struct ethhdr *)skb_push(skb, 2 * ETH_ALEN);
+	memcpy(eth->h_source, dev->dev_addr, ETH_ALEN);
+	memcpy(eth->h_dest, dest_addr, ETH_ALEN);
+
+	skb->protocol = htons(ETH_P_RMU_DSA);
+
+	dev_queue_xmit(skb);
+
+	return 0;
+}
+
+static int dsa_inband_rcv_ll(struct sk_buff *skb, struct net_device *dev)
+{
+	struct dsa_switch *ds;
+	int source_device;
+	u8 *dsa_header;
+	int rcv_seqno;
+	int ret = 0;
+
+	if (!dev || !dev->dsa_ptr)
+		return 0;
+
+	ds = dev->dsa_ptr->ds;
+	if (!ds)
+		return 0;
+
+	dsa_header = skb->data - 2;
+
+	source_device = dsa_header[0] & 0x1f;
+	ds = dsa_switch_find(ds->dst->index, source_device);
+	if (!ds) {
+		net_dbg_ratelimited("DSA inband: Didn't find switch with index %d", source_device);
+		return -EINVAL;
+	}
+
+	/* Get rcv seqno */
+	rcv_seqno = dsa_header[3];
+
+	skb_pull(skb, DSA_HLEN);
+
+	if (ds->ops && ds->ops->inband_receive(ds, skb, rcv_seqno)) {
+		dev_dbg_ratelimited(ds->dev, "DSA inband: error decoding packet");
+		ret = -EIO;
+	}
+
+	return ret;
+}
+
 static struct sk_buff *dsa_xmit_ll(struct sk_buff *skb, struct net_device *dev,
 				   u8 extra)
 {
@@ -218,9 +302,7 @@  static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev,
 
 		switch (code) {
 		case DSA_CODE_FRAME2REG:
-			/* Remote management is not implemented yet,
-			 * drop.
-			 */
+			dsa_inband_rcv_ll(skb, dev);
 			return NULL;
 		case DSA_CODE_ARP_MIRROR:
 		case DSA_CODE_POLICY_MIRROR:
@@ -325,6 +407,12 @@  static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev,
 
 #if IS_ENABLED(CONFIG_NET_DSA_TAG_DSA)
 
+static int dsa_inband_xmit(struct sk_buff *skb, struct net_device *dev,
+			   int seq_no)
+{
+	return dsa_inband_xmit_ll(skb, dev, NULL, 0, seq_no);
+}
+
 static struct sk_buff *dsa_xmit(struct sk_buff *skb, struct net_device *dev)
 {
 	return dsa_xmit_ll(skb, dev, 0);
@@ -343,6 +431,7 @@  static const struct dsa_device_ops dsa_netdev_ops = {
 	.proto	  = DSA_TAG_PROTO_DSA,
 	.xmit	  = dsa_xmit,
 	.rcv	  = dsa_rcv,
+	.inband_xmit = dsa_inband_xmit,
 	.needed_headroom = DSA_HLEN,
 };
 
@@ -354,6 +443,19 @@  MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_DSA);
 
 #define EDSA_HLEN 8
 
+static int edsa_inband_xmit(struct sk_buff *skb, struct net_device *dev,
+			    int seq_no)
+{
+	u8 edsa_header[4];
+
+	edsa_header[0] = (ETH_P_EDSA >> 8) & 0xff;
+	edsa_header[1] = ETH_P_EDSA & 0xff;
+	edsa_header[2] = 0x00;
+	edsa_header[3] = 0x00;
+
+	return dsa_inband_xmit_ll(skb, dev, edsa_header, 4, seq_no);
+}
+
 static struct sk_buff *edsa_xmit(struct sk_buff *skb, struct net_device *dev)
 {
 	u8 *edsa_header;
@@ -385,6 +487,7 @@  static const struct dsa_device_ops edsa_netdev_ops = {
 	.proto	  = DSA_TAG_PROTO_EDSA,
 	.xmit	  = edsa_xmit,
 	.rcv	  = edsa_rcv,
+	.inband_xmit = edsa_inband_xmit,
 	.needed_headroom = EDSA_HLEN,
 };