FILE
mp.c
File preview with syntax highlighting.
linux-6.19.14/drivers/net/ethernet/mellanox/mlx5/core/lag/mp.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.