Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 726fd42f authored by Arkadi Sharshevsky's avatar Arkadi Sharshevsky Committed by David S. Miller
Browse files

rocker: Add support for learning FDB through notification



Add support for learning FDB through notification. The driver defers
the hardware update via ordered work queue.

Signed-off-by: default avatarArkadi Sharshevsky <arkadis@mellanox.com>
Signed-off-by: default avatarJiri Pirko <jiri@mellanox.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 00fc0c51
Loading
Loading
Loading
Loading
+135 −5
Original line number Diff line number Diff line
@@ -1690,6 +1690,29 @@ rocker_world_port_obj_fdb_del(struct rocker_port *rocker_port,
	return wops->port_obj_fdb_del(rocker_port, fdb->vid, fdb->addr);
}

static int
rocker_world_port_fdb_add(struct rocker_port *rocker_port,
			  struct switchdev_notifier_fdb_info *info)
{
	struct rocker_world_ops *wops = rocker_port->rocker->wops;

	if (!wops->port_obj_fdb_add)
		return -EOPNOTSUPP;

	return wops->port_obj_fdb_add(rocker_port, info->vid, info->addr);
}

static int
rocker_world_port_fdb_del(struct rocker_port *rocker_port,
			  struct switchdev_notifier_fdb_info *info)
{
	struct rocker_world_ops *wops = rocker_port->rocker->wops;

	if (!wops->port_obj_fdb_del)
		return -EOPNOTSUPP;
	return wops->port_obj_fdb_del(rocker_port, info->vid, info->addr);
}

static int
rocker_world_port_obj_fdb_dump(const struct rocker_port *rocker_port,
			       struct switchdev_obj_port_fdb *fdb,
@@ -2767,6 +2790,109 @@ static void rocker_msix_fini(const struct rocker *rocker)
	kfree(rocker->msix_entries);
}

static bool rocker_port_dev_check(const struct net_device *dev)
{
	return dev->netdev_ops == &rocker_port_netdev_ops;
}

struct rocker_switchdev_event_work {
	struct work_struct work;
	struct switchdev_notifier_fdb_info fdb_info;
	struct rocker_port *rocker_port;
	unsigned long event;
};

static void
rocker_fdb_offload_notify(struct rocker_port *rocker_port,
			  struct switchdev_notifier_fdb_info *recv_info)
{
	struct switchdev_notifier_fdb_info info;

	info.addr = recv_info->addr;
	info.vid = recv_info->vid;
	call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED,
				 rocker_port->dev, &info.info);
}

static void rocker_switchdev_event_work(struct work_struct *work)
{
	struct rocker_switchdev_event_work *switchdev_work =
		container_of(work, struct rocker_switchdev_event_work, work);
	struct rocker_port *rocker_port = switchdev_work->rocker_port;
	struct switchdev_notifier_fdb_info *fdb_info;
	int err;

	rtnl_lock();
	switch (switchdev_work->event) {
	case SWITCHDEV_FDB_ADD_TO_DEVICE:
		fdb_info = &switchdev_work->fdb_info;
		err = rocker_world_port_fdb_add(rocker_port, fdb_info);
		if (err) {
			netdev_dbg(rocker_port->dev, "fdb add failed err=%d\n", err);
			break;
		}
		rocker_fdb_offload_notify(rocker_port, fdb_info);
		break;
	case SWITCHDEV_FDB_DEL_TO_DEVICE:
		fdb_info = &switchdev_work->fdb_info;
		err = rocker_world_port_fdb_del(rocker_port, fdb_info);
		if (err)
			netdev_dbg(rocker_port->dev, "fdb add failed err=%d\n", err);
		break;
	}
	rtnl_unlock();

	kfree(switchdev_work->fdb_info.addr);
	kfree(switchdev_work);
	dev_put(rocker_port->dev);
}

/* called under rcu_read_lock() */
static int rocker_switchdev_event(struct notifier_block *unused,
				  unsigned long event, void *ptr)
{
	struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
	struct rocker_switchdev_event_work *switchdev_work;
	struct switchdev_notifier_fdb_info *fdb_info = ptr;
	struct rocker_port *rocker_port;

	if (!rocker_port_dev_check(dev))
		return NOTIFY_DONE;

	rocker_port = netdev_priv(dev);
	switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
	if (WARN_ON(!switchdev_work))
		return NOTIFY_BAD;

	INIT_WORK(&switchdev_work->work, rocker_switchdev_event_work);
	switchdev_work->rocker_port = rocker_port;
	switchdev_work->event = event;

	switch (event) {
	case SWITCHDEV_FDB_ADD_TO_DEVICE: /* fall through */
	case SWITCHDEV_FDB_DEL_TO_DEVICE:
		memcpy(&switchdev_work->fdb_info, ptr,
		       sizeof(switchdev_work->fdb_info));
		switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
		ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
				fdb_info->addr);
		/* Take a reference on the rocker device */
		dev_hold(dev);
		break;
	default:
		kfree(switchdev_work);
		return NOTIFY_DONE;
	}

	queue_work(rocker_port->rocker->rocker_owq,
		   &switchdev_work->work);
	return NOTIFY_DONE;
}

static struct notifier_block rocker_switchdev_notifier = {
	.notifier_call = rocker_switchdev_event,
};

static int rocker_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
	struct rocker *rocker;
@@ -2872,6 +2998,12 @@ static int rocker_probe(struct pci_dev *pdev, const struct pci_device_id *id)
	if (err)
		goto err_register_fib_notifier;

	err = register_switchdev_notifier(&rocker_switchdev_notifier);
	if (err) {
		dev_err(&pdev->dev, "Failed to register switchdev notifier\n");
		goto err_register_switchdev_notifier;
	}

	rocker->hw.id = rocker_read64(rocker, SWITCH_ID);

	err = rocker_probe_ports(rocker);
@@ -2886,6 +3018,8 @@ static int rocker_probe(struct pci_dev *pdev, const struct pci_device_id *id)
	return 0;

err_probe_ports:
	unregister_switchdev_notifier(&rocker_switchdev_notifier);
err_register_switchdev_notifier:
	unregister_fib_notifier(&rocker->fib_nb);
err_register_fib_notifier:
	destroy_workqueue(rocker->rocker_owq);
@@ -2916,6 +3050,7 @@ static void rocker_remove(struct pci_dev *pdev)
	struct rocker *rocker = pci_get_drvdata(pdev);

	rocker_remove_ports(rocker);
	unregister_switchdev_notifier(&rocker_switchdev_notifier);
	unregister_fib_notifier(&rocker->fib_nb);
	rocker_write32(rocker, CONTROL, ROCKER_CONTROL_RESET);
	destroy_workqueue(rocker->rocker_owq);
@@ -2940,11 +3075,6 @@ static struct pci_driver rocker_pci_driver = {
 * Net device notifier event handler
 ************************************/

static bool rocker_port_dev_check(const struct net_device *dev)
{
	return dev->netdev_ops == &rocker_port_netdev_ops;
}

static bool rocker_port_dev_check_under(const struct net_device *dev,
					struct rocker *rocker)
{