mlxsw: spectrum_router: Implement FIB offload in deferred work
authorIdo Schimmel <idosch@mellanox.com>
Sat, 3 Dec 2016 15:45:01 +0000 (16:45 +0100)
committerDavid S. Miller <davem@davemloft.net>
Sun, 4 Dec 2016 00:29:35 +0000 (19:29 -0500)
FIB offload is currently done in process context with RTNL held, but
we're about to dump the FIB tables in RCU critical section, so we can no
longer sleep.

Instead, defer the operation to process context using deferred work. Make
sure fib info isn't freed while the work is queued by taking a reference
on it and releasing it after the operation is done.

Deferring the operation is valid because the upper layers always assume
the operation was successful. If it's not, then the driver-specific
abort mechanism is called and all routed traffic is directed to slow
path.

The work items are submitted to an ordered workqueue to prevent a
mismatch between the kernel's FIB table and the device's.

Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c

index 683f0454170c713df33227e8ccb2f023aa9487c1..14bed1d10b729edc77ce6780f729eb06aa4658ac 100644 (file)
@@ -593,6 +593,14 @@ static void mlxsw_sp_router_fib_flush(struct mlxsw_sp *mlxsw_sp);
 
 static void mlxsw_sp_vrs_fini(struct mlxsw_sp *mlxsw_sp)
 {
+       /* At this stage we're guaranteed not to have new incoming
+        * FIB notifications and the work queue is free from FIBs
+        * sitting on top of mlxsw netdevs. However, we can still
+        * have other FIBs queued. Flush the queue before flushing
+        * the device's tables. No need for locks, as we're the only
+        * writer.
+        */
+       mlxsw_core_flush_owq();
        mlxsw_sp_router_fib_flush(mlxsw_sp);
        kfree(mlxsw_sp->router.vrs);
 }
@@ -1948,30 +1956,74 @@ static void __mlxsw_sp_router_fini(struct mlxsw_sp *mlxsw_sp)
        kfree(mlxsw_sp->rifs);
 }
 
-static int mlxsw_sp_router_fib_event(struct notifier_block *nb,
-                                    unsigned long event, void *ptr)
+struct mlxsw_sp_fib_event_work {
+       struct delayed_work dw;
+       struct fib_entry_notifier_info fen_info;
+       struct mlxsw_sp *mlxsw_sp;
+       unsigned long event;
+};
+
+static void mlxsw_sp_router_fib_event_work(struct work_struct *work)
 {
-       struct mlxsw_sp *mlxsw_sp = container_of(nb, struct mlxsw_sp, fib_nb);
-       struct fib_entry_notifier_info *fen_info = ptr;
+       struct mlxsw_sp_fib_event_work *fib_work =
+               container_of(work, struct mlxsw_sp_fib_event_work, dw.work);
+       struct mlxsw_sp *mlxsw_sp = fib_work->mlxsw_sp;
        int err;
 
-       if (!net_eq(fen_info->info.net, &init_net))
-               return NOTIFY_DONE;
-
-       switch (event) {
+       /* Protect internal structures from changes */
+       rtnl_lock();
+       switch (fib_work->event) {
        case FIB_EVENT_ENTRY_ADD:
-               err = mlxsw_sp_router_fib4_add(mlxsw_sp, fen_info);
+               err = mlxsw_sp_router_fib4_add(mlxsw_sp, &fib_work->fen_info);
                if (err)
                        mlxsw_sp_router_fib4_abort(mlxsw_sp);
+               fib_info_put(fib_work->fen_info.fi);
                break;
        case FIB_EVENT_ENTRY_DEL:
-               mlxsw_sp_router_fib4_del(mlxsw_sp, fen_info);
+               mlxsw_sp_router_fib4_del(mlxsw_sp, &fib_work->fen_info);
+               fib_info_put(fib_work->fen_info.fi);
                break;
        case FIB_EVENT_RULE_ADD: /* fall through */
        case FIB_EVENT_RULE_DEL:
                mlxsw_sp_router_fib4_abort(mlxsw_sp);
                break;
        }
+       rtnl_unlock();
+       kfree(fib_work);
+}
+
+/* Called with rcu_read_lock() */
+static int mlxsw_sp_router_fib_event(struct notifier_block *nb,
+                                    unsigned long event, void *ptr)
+{
+       struct mlxsw_sp *mlxsw_sp = container_of(nb, struct mlxsw_sp, fib_nb);
+       struct mlxsw_sp_fib_event_work *fib_work;
+       struct fib_notifier_info *info = ptr;
+
+       if (!net_eq(info->net, &init_net))
+               return NOTIFY_DONE;
+
+       fib_work = kzalloc(sizeof(*fib_work), GFP_ATOMIC);
+       if (WARN_ON(!fib_work))
+               return NOTIFY_BAD;
+
+       INIT_DELAYED_WORK(&fib_work->dw, mlxsw_sp_router_fib_event_work);
+       fib_work->mlxsw_sp = mlxsw_sp;
+       fib_work->event = event;
+
+       switch (event) {
+       case FIB_EVENT_ENTRY_ADD: /* fall through */
+       case FIB_EVENT_ENTRY_DEL:
+               memcpy(&fib_work->fen_info, ptr, sizeof(fib_work->fen_info));
+               /* Take referece on fib_info to prevent it from being
+                * freed while work is queued. Release it afterwards.
+                */
+               fib_info_hold(fib_work->fen_info.fi);
+               break;
+       }
+
+       mlxsw_core_schedule_odw(&fib_work->dw, 0);
+
        return NOTIFY_DONE;
 }