Skip to content

Commit ceb3a75

Browse files
mdegeNipaLocal
authored andcommitted
net: renesas: rswitch: add offloading for L2 switching
Add hardware offloading for L2 switching on R-Car S4. On S4 brdev is limited to one per-device (not per port). Reasoning is that hw L2 forwarding support lacks any sort of source port based filtering, which makes it unusable to offload more than one bridge device. Either you allow hardware to forward destination MAC to a port, or you have to send it to CPU. You can't make it forward only if src and dst ports are in the same brdev. Signed-off-by: Nikita Yushchenko <[email protected]> Signed-off-by: Michael Dege <[email protected]> Signed-off-by: NipaLocal <nipa@local>
1 parent 7da1a38 commit ceb3a75

File tree

5 files changed

+412
-5
lines changed

5 files changed

+412
-5
lines changed

drivers/net/ethernet/renesas/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ obj-$(CONFIG_SH_ETH) += sh_eth.o
88
ravb-objs := ravb_main.o ravb_ptp.o
99
obj-$(CONFIG_RAVB) += ravb.o
1010

11-
rswitch-objs := rswitch_main.o
11+
rswitch-objs := rswitch_main.o rswitch_l2.o
1212
obj-$(CONFIG_RENESAS_ETHER_SWITCH) += rswitch.o
1313

1414
obj-$(CONFIG_RENESAS_GEN4_PTP) += rcar_gen4_ptp.o

drivers/net/ethernet/renesas/rswitch.h

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,18 @@
88
#define __RSWITCH_H__
99

1010
#include <linux/platform_device.h>
11+
#include <linux/phy.h>
12+
1113
#include "rcar_gen4_ptp.h"
1214

1315
#define RSWITCH_MAX_NUM_QUEUES 128
1416

1517
#define RSWITCH_NUM_AGENTS 5
1618
#define RSWITCH_NUM_PORTS 3
19+
20+
#define rswitch_for_all_ports(_priv, _rdev) \
21+
list_for_each_entry(_rdev, &_priv->port_list, list)
22+
1723
#define rswitch_for_each_enabled_port(priv, i) \
1824
for (i = 0; i < RSWITCH_NUM_PORTS; i++) \
1925
if (priv->rdev[i]->disabled) \
@@ -809,7 +815,8 @@ enum rswitch_gwca_mode {
809815
#define FWPC0_IP4EA BIT(10)
810816
#define FWPC0_IPDSA BIT(12)
811817
#define FWPC0_IPHLA BIT(18)
812-
#define FWPC0_MACSDA BIT(20)
818+
#define FWPC0_MACDSA BIT(20)
819+
#define FWPC0_MACSSA BIT(23)
813820
#define FWPC0_MACHLA BIT(26)
814821
#define FWPC0_MACHMA BIT(27)
815822
#define FWPC0_VLANSA BIT(28)
@@ -820,12 +827,18 @@ enum rswitch_gwca_mode {
820827

821828
#define FWPC2(i) (FWPC20 + (i) * 0x10)
822829
#define FWCP2_LTWFW GENMASK(16 + (RSWITCH_NUM_AGENTS - 1), 16)
830+
#define FWCP2_LTWFW_MASK GENMASK(16 + (RSWITCH_NUM_AGENTS - 1), 16)
823831

824832
#define FWPBFC(i) (FWPBFC0 + (i) * 0x10)
825833
#define FWPBFC_PBDV GENMASK(RSWITCH_NUM_AGENTS - 1, 0)
826834

827835
#define FWPBFCSDC(j, i) (FWPBFCSDC00 + (i) * 0x10 + (j) * 0x04)
828836

837+
#define FWMACHEC_MACHMUE_MASK GENMASK(26, 16)
838+
839+
#define FWMACTIM_MACTIOG BIT(0)
840+
#define FWMACTIM_MACTR BIT(1)
841+
829842
#define FWMACAGUSPC_MACAGUSP GENMASK(9, 0)
830843
#define FWMACAGC_MACAGT GENMASK(15, 0)
831844
#define FWMACAGC_MACAGE BIT(16)
@@ -1006,10 +1019,18 @@ struct rswitch_device {
10061019
DECLARE_BITMAP(ts_skb_used, TS_TAGS_PER_PORT);
10071020
bool disabled;
10081021

1022+
struct list_head list;
1023+
10091024
int port;
10101025
struct rswitch_etha *etha;
10111026
struct device_node *np_port;
10121027
struct phy *serdes;
1028+
1029+
struct net_device *brdev; /* master bridge device */
1030+
unsigned int learning_requested : 1;
1031+
unsigned int learning_offloaded : 1;
1032+
unsigned int forwarding_requested : 1;
1033+
unsigned int forwarding_offloaded : 1;
10131034
};
10141035

10151036
struct rswitch_mfwd_mac_table_entry {
@@ -1034,11 +1055,17 @@ struct rswitch_private {
10341055
struct rswitch_etha etha[RSWITCH_NUM_PORTS];
10351056
struct rswitch_mfwd mfwd;
10361057

1058+
struct list_head port_list;
1059+
10371060
spinlock_t lock; /* lock interrupt registers' control */
10381061
struct clk *clk;
10391062

10401063
bool etha_no_runtime_change;
10411064
bool gwca_halt;
1065+
struct net_device *offload_brdev;
10421066
};
10431067

1068+
bool is_rdev(const struct net_device *ndev);
1069+
void rswitch_modify(void __iomem *addr, enum rswitch_reg reg, u32 clear, u32 set);
1070+
10441071
#endif /* #ifndef __RSWITCH_H__ */
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Renesas Ethernet Switch device driver
3+
*
4+
* Copyright (C) 2025 Renesas Electronics Corporation
5+
*/
6+
7+
#include <linux/err.h>
8+
#include <linux/etherdevice.h>
9+
#include <linux/if_bridge.h>
10+
#include <linux/kernel.h>
11+
#include <net/switchdev.h>
12+
13+
#include "rswitch.h"
14+
#include "rswitch_l2.h"
15+
16+
static bool rdev_for_l2_offload(struct rswitch_device *rdev)
17+
{
18+
return rdev->priv->offload_brdev &&
19+
rdev->brdev == rdev->priv->offload_brdev &&
20+
(test_bit(rdev->port, rdev->priv->opened_ports));
21+
}
22+
23+
static void rswitch_change_l2_hw_offloading(struct rswitch_device *rdev,
24+
bool start, bool learning)
25+
{
26+
u32 bits = learning ? FWPC0_MACSSA | FWPC0_MACHLA | FWPC0_MACHMA : FWPC0_MACDSA;
27+
u32 clear = start ? 0 : bits;
28+
u32 set = start ? bits : 0;
29+
30+
if ((learning && rdev->learning_offloaded == start) ||
31+
(!learning && rdev->forwarding_offloaded == start))
32+
return;
33+
34+
rswitch_modify(rdev->priv->addr, FWPC0(rdev->port), clear, set);
35+
36+
if (learning)
37+
rdev->learning_offloaded = start;
38+
else
39+
rdev->forwarding_offloaded = start;
40+
41+
netdev_info(rdev->ndev, "%s hw %s\n", start ? "starting" : "stopping",
42+
learning ? "learning" : "forwarding");
43+
}
44+
45+
static void rswitch_update_l2_hw_learning(struct rswitch_private *priv)
46+
{
47+
struct rswitch_device *rdev;
48+
bool learning_needed;
49+
50+
rswitch_for_all_ports(priv, rdev) {
51+
if (rdev_for_l2_offload(rdev))
52+
learning_needed = rdev->learning_requested;
53+
else
54+
learning_needed = false;
55+
56+
rswitch_change_l2_hw_offloading(rdev, learning_needed, true);
57+
}
58+
}
59+
60+
static void rswitch_update_l2_hw_forwarding(struct rswitch_private *priv)
61+
{
62+
struct rswitch_device *rdev;
63+
unsigned int fwd_mask;
64+
65+
/* calculate fwd_mask with zeroes in bits corresponding to ports that
66+
* shall participate in hardware forwarding
67+
*/
68+
fwd_mask = GENMASK(RSWITCH_NUM_AGENTS - 1, 0);
69+
70+
rswitch_for_all_ports(priv, rdev) {
71+
if (rdev_for_l2_offload(rdev) && rdev->forwarding_requested)
72+
fwd_mask &= ~BIT(rdev->port);
73+
}
74+
75+
rswitch_for_all_ports(priv, rdev) {
76+
if ((rdev_for_l2_offload(rdev) && rdev->forwarding_requested) ||
77+
rdev->forwarding_offloaded) {
78+
/* Update allowed offload destinations even for ports
79+
* with L2 offload enabled earlier.
80+
*
81+
* Do not allow L2 forwarding to self for hw port.
82+
*/
83+
iowrite32(FIELD_PREP(FWCP2_LTWFW_MASK, fwd_mask | BIT(rdev->port)),
84+
priv->addr + FWPC2(rdev->port));
85+
}
86+
87+
if (rdev_for_l2_offload(rdev) &&
88+
rdev->forwarding_requested &&
89+
!rdev->forwarding_offloaded) {
90+
rswitch_change_l2_hw_offloading(rdev, true, false);
91+
} else if (rdev->forwarding_offloaded) {
92+
rswitch_change_l2_hw_offloading(rdev, false, false);
93+
}
94+
}
95+
}
96+
97+
void rswitch_update_l2_offload(struct rswitch_private *priv)
98+
{
99+
rswitch_update_l2_hw_learning(priv);
100+
rswitch_update_l2_hw_forwarding(priv);
101+
}
102+
103+
static void rswitch_update_offload_brdev(struct rswitch_private *priv)
104+
{
105+
struct net_device *offload_brdev = NULL;
106+
struct rswitch_device *rdev, *rdev2;
107+
108+
rswitch_for_all_ports(priv, rdev) {
109+
if (!rdev->brdev)
110+
continue;
111+
rswitch_for_all_ports(priv, rdev2) {
112+
if (rdev2 == rdev)
113+
break;
114+
if (rdev2->brdev == rdev->brdev) {
115+
offload_brdev = rdev->brdev;
116+
break;
117+
}
118+
}
119+
if (offload_brdev)
120+
break;
121+
}
122+
123+
if (offload_brdev == priv->offload_brdev)
124+
dev_dbg(&priv->pdev->dev,
125+
"changing l2 offload from %s to %s\n",
126+
netdev_name(priv->offload_brdev),
127+
netdev_name(offload_brdev));
128+
else if (offload_brdev)
129+
dev_dbg(&priv->pdev->dev, "starting l2 offload for %s\n",
130+
netdev_name(offload_brdev));
131+
else if (!offload_brdev)
132+
dev_dbg(&priv->pdev->dev, "stopping l2 offload for %s\n",
133+
netdev_name(priv->offload_brdev));
134+
135+
priv->offload_brdev = offload_brdev;
136+
137+
rswitch_update_l2_offload(priv);
138+
}
139+
140+
static bool rswitch_port_check(const struct net_device *ndev)
141+
{
142+
return is_rdev(ndev);
143+
}
144+
145+
static void rswitch_port_update_brdev(struct net_device *ndev,
146+
struct net_device *brdev)
147+
{
148+
struct rswitch_device *rdev;
149+
150+
if (!is_rdev(ndev))
151+
return;
152+
153+
rdev = netdev_priv(ndev);
154+
rdev->brdev = brdev;
155+
rswitch_update_offload_brdev(rdev->priv);
156+
}
157+
158+
static int rswitch_port_update_stp_state(struct net_device *ndev, u8 stp_state)
159+
{
160+
struct rswitch_device *rdev;
161+
162+
if (!is_rdev(ndev))
163+
return -ENODEV;
164+
165+
rdev = netdev_priv(ndev);
166+
rdev->learning_requested = (stp_state == BR_STATE_LEARNING ||
167+
stp_state == BR_STATE_FORWARDING);
168+
rdev->forwarding_requested = (stp_state == BR_STATE_FORWARDING);
169+
rswitch_update_l2_offload(rdev->priv);
170+
171+
return 0;
172+
}
173+
174+
static int rswitch_netdevice_event(struct notifier_block *nb,
175+
unsigned long event, void *ptr)
176+
{
177+
struct net_device *ndev = netdev_notifier_info_to_dev(ptr);
178+
struct netdev_notifier_changeupper_info *info;
179+
struct net_device *brdev;
180+
181+
if (!rswitch_port_check(ndev))
182+
return NOTIFY_DONE;
183+
if (event != NETDEV_CHANGEUPPER)
184+
return NOTIFY_DONE;
185+
186+
info = ptr;
187+
188+
if (netif_is_bridge_master(info->upper_dev)) {
189+
brdev = info->linking ? info->upper_dev : NULL;
190+
rswitch_port_update_brdev(ndev, brdev);
191+
}
192+
193+
return NOTIFY_OK;
194+
}
195+
196+
static int rswitch_port_attr_set(struct net_device *ndev, const void *ctx,
197+
const struct switchdev_attr *attr,
198+
struct netlink_ext_ack *extack)
199+
{
200+
switch (attr->id) {
201+
case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
202+
return rswitch_port_update_stp_state(ndev, attr->u.stp_state);
203+
default:
204+
return -EOPNOTSUPP;
205+
}
206+
}
207+
208+
static int rswitch_switchdev_event(struct notifier_block *nb,
209+
unsigned long event, void *ptr)
210+
{
211+
struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
212+
int ret;
213+
214+
if (event == SWITCHDEV_PORT_ATTR_SET) {
215+
ret = switchdev_handle_port_attr_set(ndev, ptr,
216+
rswitch_port_check,
217+
rswitch_port_attr_set);
218+
return notifier_from_errno(ret);
219+
}
220+
221+
if (!rswitch_port_check(ndev))
222+
return NOTIFY_DONE;
223+
224+
return notifier_from_errno(-EOPNOTSUPP);
225+
}
226+
227+
static int rswitch_switchdev_blocking_event(struct notifier_block *nb,
228+
unsigned long event, void *ptr)
229+
{
230+
struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
231+
int ret;
232+
233+
switch (event) {
234+
case SWITCHDEV_PORT_OBJ_ADD:
235+
return -EOPNOTSUPP;
236+
case SWITCHDEV_PORT_OBJ_DEL:
237+
return -EOPNOTSUPP;
238+
case SWITCHDEV_PORT_ATTR_SET:
239+
ret = switchdev_handle_port_attr_set(ndev, ptr,
240+
rswitch_port_check,
241+
rswitch_port_attr_set);
242+
break;
243+
default:
244+
if (!rswitch_port_check(ndev))
245+
return NOTIFY_DONE;
246+
ret = -EOPNOTSUPP;
247+
}
248+
249+
return notifier_from_errno(ret);
250+
}
251+
252+
static struct notifier_block rswitch_netdevice_nb = {
253+
.notifier_call = rswitch_netdevice_event,
254+
};
255+
256+
static struct notifier_block rswitch_switchdev_nb = {
257+
.notifier_call = rswitch_switchdev_event,
258+
};
259+
260+
static struct notifier_block rswitch_switchdev_blocking_nb = {
261+
.notifier_call = rswitch_switchdev_blocking_event,
262+
};
263+
264+
int rswitch_register_notifiers(void)
265+
{
266+
int ret;
267+
268+
ret = register_netdevice_notifier(&rswitch_netdevice_nb);
269+
if (ret)
270+
goto register_netdevice_notifier_failed;
271+
272+
ret = register_switchdev_notifier(&rswitch_switchdev_nb);
273+
if (ret)
274+
goto register_switchdev_notifier_failed;
275+
276+
ret = register_switchdev_blocking_notifier(&rswitch_switchdev_blocking_nb);
277+
if (ret)
278+
goto register_switchdev_blocking_notifier_failed;
279+
280+
return 0;
281+
282+
register_switchdev_blocking_notifier_failed:
283+
unregister_switchdev_notifier(&rswitch_switchdev_nb);
284+
register_switchdev_notifier_failed:
285+
unregister_netdevice_notifier(&rswitch_netdevice_nb);
286+
register_netdevice_notifier_failed:
287+
288+
return ret;
289+
}
290+
291+
void rswitch_unregister_notifiers(void)
292+
{
293+
unregister_switchdev_blocking_notifier(&rswitch_switchdev_blocking_nb);
294+
unregister_switchdev_notifier(&rswitch_switchdev_nb);
295+
unregister_netdevice_notifier(&rswitch_netdevice_nb);
296+
}

0 commit comments

Comments
 (0)