File Index
Files and directories
Sign in
FILE

mp.c

File preview with syntax highlighting.

linux-6.19.14/drivers/net/ethernet/mellanox/mlx5/core/lag/mp.c C
  1// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
  2/* Copyright (c) 2019 Mellanox Technologies. */
  3
  4#include <linux/netdevice.h>
  5#include <net/nexthop.h>
  6#include "lag/lag.h"
  7#include "lag/mp.h"
  8#include "mlx5_core.h"
  9#include "eswitch.h"
 10#include "lib/events.h"
 11
 12static bool __mlx5_lag_is_multipath(struct mlx5_lag *ldev)
 13{
 14	return ldev->mode == MLX5_LAG_MODE_MULTIPATH;
 15}
 16
 17#define MLX5_LAG_MULTIPATH_OFFLOADS_SUPPORTED_PORTS 2
 18static bool mlx5_lag_multipath_check_prereq(struct mlx5_lag *ldev)
 19{
 20	int idx0 = mlx5_lag_get_dev_index_by_seq(ldev, MLX5_LAG_P1);
 21	int idx1 = mlx5_lag_get_dev_index_by_seq(ldev, MLX5_LAG_P2);
 22
 23	if (idx0 < 0 || idx1 < 0 || !mlx5_lag_is_ready(ldev))
 24		return false;
 25
 26	if (__mlx5_lag_is_active(ldev) && !__mlx5_lag_is_multipath(ldev))
 27		return false;
 28
 29	if (ldev->ports > MLX5_LAG_MULTIPATH_OFFLOADS_SUPPORTED_PORTS)
 30		return false;
 31
 32	return mlx5_esw_multipath_prereq(ldev->pf[idx0].dev,
 33					 ldev->pf[idx1].dev);
 34}
 35
 36bool mlx5_lag_is_multipath(struct mlx5_core_dev *dev)
 37{
 38	struct mlx5_lag *ldev = mlx5_lag_dev(dev);
 39
 40	return ldev && __mlx5_lag_is_multipath(ldev);
 41}
 42
 43/**
 44 * mlx5_lag_set_port_affinity
 45 *
 46 * @ldev: lag device
 47 * @port:
 48 *     0 - set normal affinity.
 49 *     1 - set affinity to port 1.
 50 *     2 - set affinity to port 2.
 51 *
 52 **/
 53static void mlx5_lag_set_port_affinity(struct mlx5_lag *ldev,
 54				       enum mlx5_lag_port_affinity port)
 55{
 56	int idx0 = mlx5_lag_get_dev_index_by_seq(ldev, MLX5_LAG_P1);
 57	int idx1 = mlx5_lag_get_dev_index_by_seq(ldev, MLX5_LAG_P2);
 58	struct lag_tracker tracker = {};
 59
 60	if (idx0 < 0 || idx1 < 0 || !__mlx5_lag_is_multipath(ldev))
 61		return;
 62
 63	switch (port) {
 64	case MLX5_LAG_NORMAL_AFFINITY:
 65		tracker.netdev_state[idx0].tx_enabled = true;
 66		tracker.netdev_state[idx1].tx_enabled = true;
 67		tracker.netdev_state[idx0].link_up = true;
 68		tracker.netdev_state[idx1].link_up = true;
 69		break;
 70	case MLX5_LAG_P1_AFFINITY:
 71		tracker.netdev_state[idx0].tx_enabled = true;
 72		tracker.netdev_state[idx0].link_up = true;
 73		tracker.netdev_state[idx1].tx_enabled = false;
 74		tracker.netdev_state[idx1].link_up = false;
 75		break;
 76	case MLX5_LAG_P2_AFFINITY:
 77		tracker.netdev_state[idx0].tx_enabled = false;
 78		tracker.netdev_state[idx0].link_up = false;
 79		tracker.netdev_state[idx1].tx_enabled = true;
 80		tracker.netdev_state[idx1].link_up = true;
 81		break;
 82	default:
 83		mlx5_core_warn(ldev->pf[idx0].dev,
 84			       "Invalid affinity port %d", port);
 85		return;
 86	}
 87
 88	if (tracker.netdev_state[idx0].tx_enabled)
 89		mlx5_notifier_call_chain(ldev->pf[idx0].dev->priv.events,
 90					 MLX5_DEV_EVENT_PORT_AFFINITY,
 91					 (void *)0);
 92
 93	if (tracker.netdev_state[idx1].tx_enabled)
 94		mlx5_notifier_call_chain(ldev->pf[idx1].dev->priv.events,
 95					 MLX5_DEV_EVENT_PORT_AFFINITY,
 96					 (void *)0);
 97
 98	mlx5_modify_lag(ldev, &tracker);
 99}
100
101static void mlx5_lag_fib_event_flush(struct notifier_block *nb)
102{
103	struct lag_mp *mp = container_of(nb, struct lag_mp, fib_nb);
104
105	flush_workqueue(mp->wq);
106}
107
108static void mlx5_lag_fib_set(struct lag_mp *mp, struct fib_info *fi, u32 dst, int dst_len)
109{
110	mp->fib.mfi = fi;
111	mp->fib.priority = fi->fib_priority;
112	mp->fib.dst = dst;
113	mp->fib.dst_len = dst_len;
114}
115
116struct mlx5_fib_event_work {
117	struct work_struct work;
118	struct mlx5_lag *ldev;
119	unsigned long event;
120	union {
121		struct fib_entry_notifier_info fen_info;
122		struct fib_nh_notifier_info fnh_info;
123	};
124};
125
126static struct net_device*
127mlx5_lag_get_next_fib_dev(struct mlx5_lag *ldev,
128			  struct fib_info *fi,
129			  struct net_device *current_dev)
130{
131	struct net_device *fib_dev;
132	int i, ldev_idx, nhs;
133
134	nhs = fib_info_num_path(fi);
135	i = 0;
136	if (current_dev) {
137		for (; i < nhs; i++) {
138			fib_dev = fib_info_nh(fi, i)->fib_nh_dev;
139			if (fib_dev == current_dev) {
140				i++;
141				break;
142			}
143		}
144	}
145	for (; i < nhs; i++) {
146		fib_dev = fib_info_nh(fi, i)->fib_nh_dev;
147		ldev_idx = mlx5_lag_dev_get_netdev_idx(ldev, fib_dev);
148		if (ldev_idx >= 0)
149			return ldev->pf[ldev_idx].netdev;
150	}
151
152	return NULL;
153}
154
155static void mlx5_lag_fib_route_event(struct mlx5_lag *ldev, unsigned long event,
156				     struct fib_entry_notifier_info *fen_info)
157{
158	int idx = mlx5_lag_get_dev_index_by_seq(ldev, MLX5_LAG_P1);
159	struct net_device *nh_dev0, *nh_dev1;
160	struct fib_info *fi = fen_info->fi;
161	struct lag_mp *mp = &ldev->lag_mp;
162	int i, dev_idx = 0;
163
164	if (idx < 0)
165		return;
166
167	/* Handle delete event */
168	if (event == FIB_EVENT_ENTRY_DEL) {
169		/* stop track */
170		if (mp->fib.mfi == fi)
171			mp->fib.mfi = NULL;
172		return;
173	}
174
175	/* Handle multipath entry with lower priority value */
176	if (mp->fib.mfi &&
177	    (mp->fib.dst != fen_info->dst || mp->fib.dst_len != fen_info->dst_len) &&
178	    mp->fib.dst_len <= fen_info->dst_len &&
179	    !(mp->fib.dst_len == fen_info->dst_len &&
180	      fi->fib_priority < mp->fib.priority)) {
181		mlx5_core_dbg(ldev->pf[idx].dev,
182			      "Multipath entry with lower priority was rejected\n");
183		return;
184	}
185
186	nh_dev0 = mlx5_lag_get_next_fib_dev(ldev, fi, NULL);
187	nh_dev1 = mlx5_lag_get_next_fib_dev(ldev, fi, nh_dev0);
188
189	/* Handle add/replace event */
190	if (!nh_dev0) {
191		if (mp->fib.dst == fen_info->dst && mp->fib.dst_len == fen_info->dst_len)
192			mp->fib.mfi = NULL;
193		return;
194	}
195
196	if (nh_dev0 == nh_dev1) {
197		mlx5_core_warn(ldev->pf[idx].dev,
198			       "Multipath offload doesn't support routes with multiple nexthops of the same device");
199		return;
200	}
201
202	if (!nh_dev1) {
203		if (__mlx5_lag_is_active(ldev)) {
204			mlx5_ldev_for_each(i, 0, ldev) {
205				dev_idx++;
206				if (ldev->pf[i].netdev == nh_dev0)
207					break;
208			}
209			mlx5_lag_set_port_affinity(ldev, dev_idx);
210			mlx5_lag_fib_set(mp, fi, fen_info->dst, fen_info->dst_len);
211		}
212
213		return;
214	}
215
216	/* First time we see multipath route */
217	if (!mp->fib.mfi && !__mlx5_lag_is_active(ldev)) {
218		struct lag_tracker tracker;
219
220		tracker = ldev->tracker;
221		mlx5_activate_lag(ldev, &tracker, MLX5_LAG_MODE_MULTIPATH, false);
222	}
223
224	mlx5_lag_set_port_affinity(ldev, MLX5_LAG_NORMAL_AFFINITY);
225	mlx5_lag_fib_set(mp, fi, fen_info->dst, fen_info->dst_len);
226}
227
228static void mlx5_lag_fib_nexthop_event(struct mlx5_lag *ldev,
229				       unsigned long event,
230				       struct fib_nh *fib_nh,
231				       struct fib_info *fi)
232{
233	struct lag_mp *mp = &ldev->lag_mp;
234	int i, dev_idx = 0;
235
236	/* Check the nh event is related to the route */
237	if (!mp->fib.mfi || mp->fib.mfi != fi)
238		return;
239
240	/* nh added/removed */
241	if (event == FIB_EVENT_NH_DEL) {
242		mlx5_ldev_for_each(i, 0, ldev) {
243			if (ldev->pf[i].netdev == fib_nh->fib_nh_dev)
244				break;
245			dev_idx++;
246		}
247
248		if (dev_idx >= 0) {
249			dev_idx = (dev_idx + 1) % 2 + 1; /* peer port */
250			mlx5_lag_set_port_affinity(ldev, dev_idx);
251		}
252	} else if (event == FIB_EVENT_NH_ADD &&
253		   fib_info_num_path(fi) == 2) {
254		mlx5_lag_set_port_affinity(ldev, MLX5_LAG_NORMAL_AFFINITY);
255	}
256}
257
258static void mlx5_lag_fib_update(struct work_struct *work)
259{
260	struct mlx5_fib_event_work *fib_work =
261		container_of(work, struct mlx5_fib_event_work, work);
262	struct mlx5_lag *ldev = fib_work->ldev;
263	struct fib_nh *fib_nh;
264
265	/* Protect internal structures from changes */
266	rtnl_lock();
267	switch (fib_work->event) {
268	case FIB_EVENT_ENTRY_REPLACE:
269	case FIB_EVENT_ENTRY_DEL:
270		mlx5_lag_fib_route_event(ldev, fib_work->event,
271					 &fib_work->fen_info);
272		fib_info_put(fib_work->fen_info.fi);
273		break;
274	case FIB_EVENT_NH_ADD:
275	case FIB_EVENT_NH_DEL:
276		fib_nh = fib_work->fnh_info.fib_nh;
277		mlx5_lag_fib_nexthop_event(ldev,
278					   fib_work->event,
279					   fib_work->fnh_info.fib_nh,
280					   fib_nh->nh_parent);
281		fib_info_put(fib_work->fnh_info.fib_nh->nh_parent);
282		break;
283	}
284
285	rtnl_unlock();
286	kfree(fib_work);
287}
288
289static struct mlx5_fib_event_work *
290mlx5_lag_init_fib_work(struct mlx5_lag *ldev, unsigned long event)
291{
292	struct mlx5_fib_event_work *fib_work;
293
294	fib_work = kzalloc(sizeof(*fib_work), GFP_ATOMIC);
295	if (WARN_ON(!fib_work))
296		return NULL;
297
298	INIT_WORK(&fib_work->work, mlx5_lag_fib_update);
299	fib_work->ldev = ldev;
300	fib_work->event = event;
301
302	return fib_work;
303}
304
305static int mlx5_lag_fib_event(struct notifier_block *nb,
306			      unsigned long event,
307			      void *ptr)
308{
309	struct lag_mp *mp = container_of(nb, struct lag_mp, fib_nb);
310	struct mlx5_lag *ldev = container_of(mp, struct mlx5_lag, lag_mp);
311	struct fib_notifier_info *info = ptr;
312	struct mlx5_fib_event_work *fib_work;
313	struct fib_entry_notifier_info *fen_info;
314	struct fib_nh_notifier_info *fnh_info;
315	struct fib_info *fi;
316
317	if (info->family != AF_INET)
318		return NOTIFY_DONE;
319
320	if (!mlx5_lag_multipath_check_prereq(ldev))
321		return NOTIFY_DONE;
322
323	switch (event) {
324	case FIB_EVENT_ENTRY_REPLACE:
325	case FIB_EVENT_ENTRY_DEL:
326		fen_info = container_of(info, struct fib_entry_notifier_info,
327					info);
328		fi = fen_info->fi;
329		if (fi->nh)
330			return NOTIFY_DONE;
331
332		fib_work = mlx5_lag_init_fib_work(ldev, event);
333		if (!fib_work)
334			return NOTIFY_DONE;
335		fib_work->fen_info = *fen_info;
336		/* Take reference on fib_info to prevent it from being
337		 * freed while work is queued. Release it afterwards.
338		 */
339		fib_info_hold(fib_work->fen_info.fi);
340		break;
341	case FIB_EVENT_NH_ADD:
342	case FIB_EVENT_NH_DEL:
343		fnh_info = container_of(info, struct fib_nh_notifier_info,
344					info);
345		fib_work = mlx5_lag_init_fib_work(ldev, event);
346		if (!fib_work)
347			return NOTIFY_DONE;
348		fib_work->fnh_info = *fnh_info;
349		fib_info_hold(fib_work->fnh_info.fib_nh->nh_parent);
350		break;
351	default:
352		return NOTIFY_DONE;
353	}
354
355	queue_work(mp->wq, &fib_work->work);
356
357	return NOTIFY_DONE;
358}
359
360void mlx5_lag_mp_reset(struct mlx5_lag *ldev)
361{
362	/* Clear mfi, as it might become stale when a route delete event
363	 * has been missed, see mlx5_lag_fib_route_event().
364	 */
365	ldev->lag_mp.fib.mfi = NULL;
366}
367
368int mlx5_lag_mp_init(struct mlx5_lag *ldev)
369{
370	struct lag_mp *mp = &ldev->lag_mp;
371	int err;
372
373	/* always clear mfi, as it might become stale when a route delete event
374	 * has been missed
375	 */
376	mp->fib.mfi = NULL;
377
378	if (mp->fib_nb.notifier_call)
379		return 0;
380
381	mp->wq = create_singlethread_workqueue("mlx5_lag_mp");
382	if (!mp->wq)
383		return -ENOMEM;
384
385	mp->fib_nb.notifier_call = mlx5_lag_fib_event;
386	err = register_fib_notifier(&init_net, &mp->fib_nb,
387				    mlx5_lag_fib_event_flush, NULL);
388	if (err) {
389		destroy_workqueue(mp->wq);
390		mp->fib_nb.notifier_call = NULL;
391	}
392
393	return err;
394}
395
396void mlx5_lag_mp_cleanup(struct mlx5_lag *ldev)
397{
398	struct lag_mp *mp = &ldev->lag_mp;
399
400	if (!mp->fib_nb.notifier_call)
401		return;
402
403	unregister_fib_notifier(&init_net, &mp->fib_nb);
404	destroy_workqueue(mp->wq);
405	mp->fib_nb.notifier_call = NULL;
406	mp->fib.mfi = NULL;
407}
~/ Read-only file preview.