|
@@ -28,6 +28,7 @@
|
|
#include <linux/if_bridge.h>
|
|
#include <linux/if_bridge.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/ctype.h>
|
|
|
|
+#include <linux/workqueue.h>
|
|
#include <net/switchdev.h>
|
|
#include <net/switchdev.h>
|
|
#include <net/rtnetlink.h>
|
|
#include <net/rtnetlink.h>
|
|
#include <net/netevent.h>
|
|
#include <net/netevent.h>
|
|
@@ -2165,28 +2166,70 @@ static const struct switchdev_ops rocker_port_switchdev_ops = {
|
|
.switchdev_port_obj_dump = rocker_port_obj_dump,
|
|
.switchdev_port_obj_dump = rocker_port_obj_dump,
|
|
};
|
|
};
|
|
|
|
|
|
-static int rocker_router_fib_event(struct notifier_block *nb,
|
|
|
|
- unsigned long event, void *ptr)
|
|
|
|
|
|
+struct rocker_fib_event_work {
|
|
|
|
+ struct work_struct work;
|
|
|
|
+ struct fib_entry_notifier_info fen_info;
|
|
|
|
+ struct rocker *rocker;
|
|
|
|
+ unsigned long event;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static void rocker_router_fib_event_work(struct work_struct *work)
|
|
{
|
|
{
|
|
- struct rocker *rocker = container_of(nb, struct rocker, fib_nb);
|
|
|
|
- struct fib_entry_notifier_info *fen_info = ptr;
|
|
|
|
|
|
+ struct rocker_fib_event_work *fib_work =
|
|
|
|
+ container_of(work, struct rocker_fib_event_work, work);
|
|
|
|
+ struct rocker *rocker = fib_work->rocker;
|
|
int err;
|
|
int err;
|
|
|
|
|
|
- switch (event) {
|
|
|
|
|
|
+ /* Protect internal structures from changes */
|
|
|
|
+ rtnl_lock();
|
|
|
|
+ switch (fib_work->event) {
|
|
case FIB_EVENT_ENTRY_ADD:
|
|
case FIB_EVENT_ENTRY_ADD:
|
|
- err = rocker_world_fib4_add(rocker, fen_info);
|
|
|
|
|
|
+ err = rocker_world_fib4_add(rocker, &fib_work->fen_info);
|
|
if (err)
|
|
if (err)
|
|
rocker_world_fib4_abort(rocker);
|
|
rocker_world_fib4_abort(rocker);
|
|
- else
|
|
|
|
|
|
+ fib_info_put(fib_work->fen_info.fi);
|
|
break;
|
|
break;
|
|
case FIB_EVENT_ENTRY_DEL:
|
|
case FIB_EVENT_ENTRY_DEL:
|
|
- rocker_world_fib4_del(rocker, fen_info);
|
|
|
|
|
|
+ rocker_world_fib4_del(rocker, &fib_work->fen_info);
|
|
|
|
+ fib_info_put(fib_work->fen_info.fi);
|
|
break;
|
|
break;
|
|
case FIB_EVENT_RULE_ADD: /* fall through */
|
|
case FIB_EVENT_RULE_ADD: /* fall through */
|
|
case FIB_EVENT_RULE_DEL:
|
|
case FIB_EVENT_RULE_DEL:
|
|
rocker_world_fib4_abort(rocker);
|
|
rocker_world_fib4_abort(rocker);
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
+ rtnl_unlock();
|
|
|
|
+ kfree(fib_work);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* Called with rcu_read_lock() */
|
|
|
|
+static int rocker_router_fib_event(struct notifier_block *nb,
|
|
|
|
+ unsigned long event, void *ptr)
|
|
|
|
+{
|
|
|
|
+ struct rocker *rocker = container_of(nb, struct rocker, fib_nb);
|
|
|
|
+ struct rocker_fib_event_work *fib_work;
|
|
|
|
+
|
|
|
|
+ fib_work = kzalloc(sizeof(*fib_work), GFP_ATOMIC);
|
|
|
|
+ if (WARN_ON(!fib_work))
|
|
|
|
+ return NOTIFY_BAD;
|
|
|
|
+
|
|
|
|
+ INIT_WORK(&fib_work->work, rocker_router_fib_event_work);
|
|
|
|
+ fib_work->rocker = rocker;
|
|
|
|
+ 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;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ queue_work(rocker->rocker_owq, &fib_work->work);
|
|
|
|
+
|
|
return NOTIFY_DONE;
|
|
return NOTIFY_DONE;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -2754,6 +2797,21 @@ static int rocker_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
|
goto err_request_event_irq;
|
|
goto err_request_event_irq;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ rocker->rocker_owq = alloc_ordered_workqueue(rocker_driver_name,
|
|
|
|
+ WQ_MEM_RECLAIM);
|
|
|
|
+ if (!rocker->rocker_owq) {
|
|
|
|
+ err = -ENOMEM;
|
|
|
|
+ goto err_alloc_ordered_workqueue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Only FIBs pointing to our own netdevs are programmed into
|
|
|
|
+ * the device, so no need to pass a callback.
|
|
|
|
+ */
|
|
|
|
+ rocker->fib_nb.notifier_call = rocker_router_fib_event;
|
|
|
|
+ err = register_fib_notifier(&rocker->fib_nb, NULL);
|
|
|
|
+ if (err)
|
|
|
|
+ goto err_register_fib_notifier;
|
|
|
|
+
|
|
rocker->hw.id = rocker_read64(rocker, SWITCH_ID);
|
|
rocker->hw.id = rocker_read64(rocker, SWITCH_ID);
|
|
|
|
|
|
err = rocker_probe_ports(rocker);
|
|
err = rocker_probe_ports(rocker);
|
|
@@ -2762,15 +2820,16 @@ static int rocker_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
|
goto err_probe_ports;
|
|
goto err_probe_ports;
|
|
}
|
|
}
|
|
|
|
|
|
- rocker->fib_nb.notifier_call = rocker_router_fib_event;
|
|
|
|
- register_fib_notifier(&rocker->fib_nb);
|
|
|
|
-
|
|
|
|
dev_info(&pdev->dev, "Rocker switch with id %*phN\n",
|
|
dev_info(&pdev->dev, "Rocker switch with id %*phN\n",
|
|
(int)sizeof(rocker->hw.id), &rocker->hw.id);
|
|
(int)sizeof(rocker->hw.id), &rocker->hw.id);
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
err_probe_ports:
|
|
err_probe_ports:
|
|
|
|
+ unregister_fib_notifier(&rocker->fib_nb);
|
|
|
|
+err_register_fib_notifier:
|
|
|
|
+ destroy_workqueue(rocker->rocker_owq);
|
|
|
|
+err_alloc_ordered_workqueue:
|
|
free_irq(rocker_msix_vector(rocker, ROCKER_MSIX_VEC_EVENT), rocker);
|
|
free_irq(rocker_msix_vector(rocker, ROCKER_MSIX_VEC_EVENT), rocker);
|
|
err_request_event_irq:
|
|
err_request_event_irq:
|
|
free_irq(rocker_msix_vector(rocker, ROCKER_MSIX_VEC_CMD), rocker);
|
|
free_irq(rocker_msix_vector(rocker, ROCKER_MSIX_VEC_CMD), rocker);
|
|
@@ -2796,9 +2855,10 @@ static void rocker_remove(struct pci_dev *pdev)
|
|
{
|
|
{
|
|
struct rocker *rocker = pci_get_drvdata(pdev);
|
|
struct rocker *rocker = pci_get_drvdata(pdev);
|
|
|
|
|
|
|
|
+ rocker_remove_ports(rocker);
|
|
unregister_fib_notifier(&rocker->fib_nb);
|
|
unregister_fib_notifier(&rocker->fib_nb);
|
|
rocker_write32(rocker, CONTROL, ROCKER_CONTROL_RESET);
|
|
rocker_write32(rocker, CONTROL, ROCKER_CONTROL_RESET);
|
|
- rocker_remove_ports(rocker);
|
|
|
|
|
|
+ destroy_workqueue(rocker->rocker_owq);
|
|
free_irq(rocker_msix_vector(rocker, ROCKER_MSIX_VEC_EVENT), rocker);
|
|
free_irq(rocker_msix_vector(rocker, ROCKER_MSIX_VEC_EVENT), rocker);
|
|
free_irq(rocker_msix_vector(rocker, ROCKER_MSIX_VEC_CMD), rocker);
|
|
free_irq(rocker_msix_vector(rocker, ROCKER_MSIX_VEC_CMD), rocker);
|
|
rocker_dma_rings_fini(rocker);
|
|
rocker_dma_rings_fini(rocker);
|