File Index
Files and directories
Sign in
FILE

rswitch_l2.c

File preview with syntax highlighting.

linux-6.19.14/drivers/net/ethernet/renesas/rswitch_l2.c C
  1// SPDX-License-Identifier: GPL-2.0
  2/* Renesas Ethernet Switch device driver
  3 *
  4 * Copyright (C) 2025 - 2026 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
 16static 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
 23static 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
 45static 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
 60static void rswitch_update_l2_hw_forwarding(struct rswitch_private *priv)
 61{
 62	struct rswitch_device *rdev;
 63	bool new_forwarding_offload;
 64	unsigned int fwd_mask;
 65
 66	/* calculate fwd_mask with zeroes in bits corresponding to ports that
 67	 * shall participate in hardware forwarding
 68	 */
 69	fwd_mask = GENMASK(RSWITCH_NUM_AGENTS - 1, 0);
 70
 71	rswitch_for_all_ports(priv, rdev) {
 72		if (rdev_for_l2_offload(rdev) && rdev->forwarding_requested)
 73			fwd_mask &= ~BIT(rdev->port);
 74	}
 75
 76	rswitch_for_all_ports(priv, rdev) {
 77		new_forwarding_offload = (rdev_for_l2_offload(rdev) && rdev->forwarding_requested);
 78
 79		if (new_forwarding_offload || rdev->forwarding_offloaded) {
 80			/* Update allowed offload destinations even for ports
 81			 * with L2 offload enabled earlier.
 82			 *
 83			 * Do not allow L2 forwarding to self for hw port.
 84			 */
 85			iowrite32(FIELD_PREP(FWCP2_LTWFW_MASK, fwd_mask | BIT(rdev->port)),
 86				  priv->addr + FWPC2(rdev->port));
 87		}
 88
 89		if (new_forwarding_offload && !rdev->forwarding_offloaded)
 90			rswitch_change_l2_hw_offloading(rdev, true, false);
 91		else if (!new_forwarding_offload && rdev->forwarding_offloaded)
 92			rswitch_change_l2_hw_offloading(rdev, false, false);
 93	}
 94}
 95
 96void rswitch_update_l2_offload(struct rswitch_private *priv)
 97{
 98	rswitch_update_l2_hw_learning(priv);
 99	rswitch_update_l2_hw_forwarding(priv);
100}
101
102static void rswitch_update_offload_brdev(struct rswitch_private *priv)
103{
104	struct net_device *offload_brdev = NULL;
105	struct rswitch_device *rdev, *rdev2;
106
107	rswitch_for_all_ports(priv, rdev) {
108		if (!rdev->brdev)
109			continue;
110		rswitch_for_all_ports(priv, rdev2) {
111			if (rdev2 == rdev)
112				break;
113			if (rdev2->brdev == rdev->brdev) {
114				offload_brdev = rdev->brdev;
115				break;
116			}
117		}
118		if (offload_brdev)
119			break;
120	}
121
122	if (offload_brdev == priv->offload_brdev)
123		dev_dbg(&priv->pdev->dev,
124			"changing l2 offload from %s to %s\n",
125			netdev_name(priv->offload_brdev),
126			netdev_name(offload_brdev));
127	else if (offload_brdev)
128		dev_dbg(&priv->pdev->dev, "starting l2 offload for %s\n",
129			netdev_name(offload_brdev));
130	else if (!offload_brdev)
131		dev_dbg(&priv->pdev->dev, "stopping l2 offload for %s\n",
132			netdev_name(priv->offload_brdev));
133
134	priv->offload_brdev = offload_brdev;
135
136	rswitch_update_l2_offload(priv);
137}
138
139static bool rswitch_port_check(const struct net_device *ndev)
140{
141	return is_rdev(ndev);
142}
143
144static void rswitch_port_update_brdev(struct net_device *ndev,
145				      struct net_device *brdev)
146{
147	struct rswitch_device *rdev;
148
149	if (!is_rdev(ndev))
150		return;
151
152	rdev = netdev_priv(ndev);
153	rdev->brdev = brdev;
154	rswitch_update_offload_brdev(rdev->priv);
155}
156
157static int rswitch_port_update_stp_state(struct net_device *ndev, u8 stp_state)
158{
159	struct rswitch_device *rdev;
160
161	if (!is_rdev(ndev))
162		return -ENODEV;
163
164	rdev = netdev_priv(ndev);
165	rdev->learning_requested = (stp_state == BR_STATE_LEARNING ||
166				    stp_state == BR_STATE_FORWARDING);
167	rdev->forwarding_requested = (stp_state == BR_STATE_FORWARDING);
168	rswitch_update_l2_offload(rdev->priv);
169
170	return 0;
171}
172
173static int rswitch_netdevice_event(struct notifier_block *nb,
174				   unsigned long event, void *ptr)
175{
176	struct net_device *ndev = netdev_notifier_info_to_dev(ptr);
177	struct netdev_notifier_changeupper_info *info;
178	struct net_device *brdev;
179
180	if (!rswitch_port_check(ndev))
181		return NOTIFY_DONE;
182	if (event != NETDEV_CHANGEUPPER)
183		return NOTIFY_DONE;
184
185	info = ptr;
186
187	if (netif_is_bridge_master(info->upper_dev)) {
188		brdev = info->linking ? info->upper_dev : NULL;
189		rswitch_port_update_brdev(ndev, brdev);
190	}
191
192	return NOTIFY_OK;
193}
194
195static int rswitch_update_ageing_time(struct net_device *ndev, clock_t time)
196{
197	struct rswitch_device *rdev = netdev_priv(ndev);
198	u32 reg_val;
199
200	if (!is_rdev(ndev))
201		return -ENODEV;
202
203	if (!FIELD_FIT(FWMACAGC_MACAGT, time))
204		return -EINVAL;
205
206	reg_val = FIELD_PREP(FWMACAGC_MACAGT, time);
207	reg_val |= FWMACAGC_MACAGE | FWMACAGC_MACAGSL;
208	iowrite32(reg_val, rdev->priv->addr + FWMACAGC);
209
210	return 0;
211}
212
213static int rswitch_port_attr_set(struct net_device *ndev, const void *ctx,
214				 const struct switchdev_attr *attr,
215				 struct netlink_ext_ack *extack)
216{
217	switch (attr->id) {
218	case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
219		return rswitch_port_update_stp_state(ndev, attr->u.stp_state);
220	case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:
221		return rswitch_update_ageing_time(ndev, attr->u.ageing_time);
222	default:
223		return -EOPNOTSUPP;
224	}
225}
226
227static int rswitch_switchdev_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	if (event == SWITCHDEV_PORT_ATTR_SET) {
234		ret = switchdev_handle_port_attr_set(ndev, ptr,
235						     rswitch_port_check,
236						     rswitch_port_attr_set);
237		return notifier_from_errno(ret);
238	}
239
240	if (!rswitch_port_check(ndev))
241		return NOTIFY_DONE;
242
243	return notifier_from_errno(-EOPNOTSUPP);
244}
245
246static int rswitch_switchdev_blocking_event(struct notifier_block *nb,
247					    unsigned long event, void *ptr)
248{
249	struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
250	int ret;
251
252	switch (event) {
253	case SWITCHDEV_PORT_OBJ_ADD:
254		return -EOPNOTSUPP;
255	case SWITCHDEV_PORT_OBJ_DEL:
256		return -EOPNOTSUPP;
257	case SWITCHDEV_PORT_ATTR_SET:
258		ret = switchdev_handle_port_attr_set(ndev, ptr,
259						     rswitch_port_check,
260						     rswitch_port_attr_set);
261		break;
262	default:
263		if (!rswitch_port_check(ndev))
264			return NOTIFY_DONE;
265		ret = -EOPNOTSUPP;
266	}
267
268	return notifier_from_errno(ret);
269}
270
271static struct notifier_block rswitch_netdevice_nb = {
272	.notifier_call = rswitch_netdevice_event,
273};
274
275static struct notifier_block rswitch_switchdev_nb = {
276	.notifier_call = rswitch_switchdev_event,
277};
278
279static struct notifier_block rswitch_switchdev_blocking_nb = {
280	.notifier_call = rswitch_switchdev_blocking_event,
281};
282
283int rswitch_register_notifiers(void)
284{
285	int ret;
286
287	ret = register_netdevice_notifier(&rswitch_netdevice_nb);
288	if (ret)
289		goto register_netdevice_notifier_failed;
290
291	ret = register_switchdev_notifier(&rswitch_switchdev_nb);
292	if (ret)
293		goto register_switchdev_notifier_failed;
294
295	ret = register_switchdev_blocking_notifier(&rswitch_switchdev_blocking_nb);
296	if (ret)
297		goto register_switchdev_blocking_notifier_failed;
298
299	return 0;
300
301register_switchdev_blocking_notifier_failed:
302	unregister_switchdev_notifier(&rswitch_switchdev_nb);
303register_switchdev_notifier_failed:
304	unregister_netdevice_notifier(&rswitch_netdevice_nb);
305register_netdevice_notifier_failed:
306
307	return ret;
308}
309
310void rswitch_unregister_notifiers(void)
311{
312	unregister_switchdev_blocking_notifier(&rswitch_switchdev_blocking_nb);
313	unregister_switchdev_notifier(&rswitch_switchdev_nb);
314	unregister_netdevice_notifier(&rswitch_netdevice_nb);
315}
~/ Read-only file preview.