Commit 0ad722bd authored by Hui Zhou's avatar Hui Zhou Committed by David S. Miller
Browse files

nfp: flower: fix for take a mutex lock in soft irq context and rcu lock



The neighbour event callback call the function nfp_tun_write_neigh,
this function will take a mutex lock and it is in soft irq context,
change the work queue to process the neighbour event.

Move the nfp_tun_write_neigh function out of range rcu_read_lock/unlock()
in function nfp_tunnel_request_route_v4 and nfp_tunnel_request_route_v6.

Fixes: abc21095 ("nfp: flower: tunnel neigh support bond offload")
CC: stable@vger.kernel.org # 6.2+
Signed-off-by: default avatarHui Zhou <hui.zhou@corigine.com>
Signed-off-by: default avatarLouis Peens <louis.peens@corigine.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 803a809d
Loading
Loading
Loading
Loading
+95 −32
Original line number Diff line number Diff line
@@ -160,6 +160,18 @@ struct nfp_tun_mac_addr_offload {
	u8 addr[ETH_ALEN];
};

/**
 * struct nfp_neigh_update_work - update neighbour information to nfp
 * @work:	Work queue for writing neigh to the nfp
 * @n:		neighbour entry
 * @app:	Back pointer to app
 */
struct nfp_neigh_update_work {
	struct work_struct work;
	struct neighbour *n;
	struct nfp_app *app;
};

enum nfp_flower_mac_offload_cmd {
	NFP_TUNNEL_MAC_OFFLOAD_ADD =		0,
	NFP_TUNNEL_MAC_OFFLOAD_DEL =		1,
@@ -607,38 +619,30 @@ nfp_tun_write_neigh(struct net_device *netdev, struct nfp_app *app,
	nfp_flower_cmsg_warn(app, "Neighbour configuration failed.\n");
}

static int
nfp_tun_neigh_event_handler(struct notifier_block *nb, unsigned long event,
			    void *ptr)
static void
nfp_tun_release_neigh_update_work(struct nfp_neigh_update_work *update_work)
{
	struct nfp_flower_priv *app_priv;
	struct netevent_redirect *redir;
	struct neighbour *n;
	neigh_release(update_work->n);
	kfree(update_work);
}

static void nfp_tun_neigh_update(struct work_struct *work)
{
	struct nfp_neigh_update_work *update_work;
	struct nfp_app *app;
	struct neighbour *n;
	bool neigh_invalid;
	int err;

	switch (event) {
	case NETEVENT_REDIRECT:
		redir = (struct netevent_redirect *)ptr;
		n = redir->neigh;
		break;
	case NETEVENT_NEIGH_UPDATE:
		n = (struct neighbour *)ptr;
		break;
	default:
		return NOTIFY_DONE;
	}

	neigh_invalid = !(n->nud_state & NUD_VALID) || n->dead;

	app_priv = container_of(nb, struct nfp_flower_priv, tun.neigh_nb);
	app = app_priv->app;
	update_work = container_of(work, struct nfp_neigh_update_work, work);
	app = update_work->app;
	n = update_work->n;

	if (!nfp_flower_get_port_id_from_netdev(app, n->dev))
		return NOTIFY_DONE;
		goto out;

#if IS_ENABLED(CONFIG_INET)
	neigh_invalid = !(n->nud_state & NUD_VALID) || n->dead;
	if (n->tbl->family == AF_INET6) {
#if IS_ENABLED(CONFIG_IPV6)
		struct flowi6 flow6 = {};
@@ -655,13 +659,11 @@ nfp_tun_neigh_event_handler(struct notifier_block *nb, unsigned long event,
			dst = ip6_dst_lookup_flow(dev_net(n->dev), NULL,
						  &flow6, NULL);
			if (IS_ERR(dst))
				return NOTIFY_DONE;
				goto out;

			dst_release(dst);
		}
		nfp_tun_write_neigh(n->dev, app, &flow6, n, true, false);
#else
		return NOTIFY_DONE;
#endif /* CONFIG_IPV6 */
	} else {
		struct flowi4 flow4 = {};
@@ -678,17 +680,71 @@ nfp_tun_neigh_event_handler(struct notifier_block *nb, unsigned long event,
			rt = ip_route_output_key(dev_net(n->dev), &flow4);
			err = PTR_ERR_OR_ZERO(rt);
			if (err)
				return NOTIFY_DONE;
				goto out;

			ip_rt_put(rt);
		}
		nfp_tun_write_neigh(n->dev, app, &flow4, n, false, false);
	}
#endif /* CONFIG_INET */
out:
	nfp_tun_release_neigh_update_work(update_work);
}

static struct nfp_neigh_update_work *
nfp_tun_alloc_neigh_update_work(struct nfp_app *app, struct neighbour *n)
{
	struct nfp_neigh_update_work *update_work;

	update_work = kzalloc(sizeof(*update_work), GFP_ATOMIC);
	if (!update_work)
		return NULL;

	INIT_WORK(&update_work->work, nfp_tun_neigh_update);
	neigh_hold(n);
	update_work->n = n;
	update_work->app = app;

	return update_work;
}

static int
nfp_tun_neigh_event_handler(struct notifier_block *nb, unsigned long event,
			    void *ptr)
{
	struct nfp_neigh_update_work *update_work;
	struct nfp_flower_priv *app_priv;
	struct netevent_redirect *redir;
	struct neighbour *n;
	struct nfp_app *app;

	switch (event) {
	case NETEVENT_REDIRECT:
		redir = (struct netevent_redirect *)ptr;
		n = redir->neigh;
		break;
	case NETEVENT_NEIGH_UPDATE:
		n = (struct neighbour *)ptr;
		break;
	default:
		return NOTIFY_DONE;
	}
#if IS_ENABLED(CONFIG_IPV6)
	if (n->tbl != ipv6_stub->nd_tbl && n->tbl != &arp_tbl)
#else
	if (n->tbl != &arp_tbl)
#endif
		return NOTIFY_DONE;
#endif /* CONFIG_INET */

	return NOTIFY_OK;
	app_priv = container_of(nb, struct nfp_flower_priv, tun.neigh_nb);
	app = app_priv->app;
	update_work = nfp_tun_alloc_neigh_update_work(app, n);
	if (!update_work)
		return NOTIFY_DONE;

	queue_work(system_highpri_wq, &update_work->work);

	return NOTIFY_DONE;
}

void nfp_tunnel_request_route_v4(struct nfp_app *app, struct sk_buff *skb)
@@ -706,6 +762,7 @@ void nfp_tunnel_request_route_v4(struct nfp_app *app, struct sk_buff *skb)
	netdev = nfp_app_dev_get(app, be32_to_cpu(payload->ingress_port), NULL);
	if (!netdev)
		goto fail_rcu_unlock;
	dev_hold(netdev);

	flow.daddr = payload->ipv4_addr;
	flow.flowi4_proto = IPPROTO_UDP;
@@ -725,13 +782,16 @@ void nfp_tunnel_request_route_v4(struct nfp_app *app, struct sk_buff *skb)
	ip_rt_put(rt);
	if (!n)
		goto fail_rcu_unlock;
	rcu_read_unlock();

	nfp_tun_write_neigh(n->dev, app, &flow, n, false, true);
	neigh_release(n);
	rcu_read_unlock();
	dev_put(netdev);
	return;

fail_rcu_unlock:
	rcu_read_unlock();
	dev_put(netdev);
	nfp_flower_cmsg_warn(app, "Requested route not found.\n");
}

@@ -749,6 +809,7 @@ void nfp_tunnel_request_route_v6(struct nfp_app *app, struct sk_buff *skb)
	netdev = nfp_app_dev_get(app, be32_to_cpu(payload->ingress_port), NULL);
	if (!netdev)
		goto fail_rcu_unlock;
	dev_hold(netdev);

	flow.daddr = payload->ipv6_addr;
	flow.flowi6_proto = IPPROTO_UDP;
@@ -766,14 +827,16 @@ void nfp_tunnel_request_route_v6(struct nfp_app *app, struct sk_buff *skb)
	dst_release(dst);
	if (!n)
		goto fail_rcu_unlock;
	rcu_read_unlock();

	nfp_tun_write_neigh(n->dev, app, &flow, n, true, true);
	neigh_release(n);
	rcu_read_unlock();
	dev_put(netdev);
	return;

fail_rcu_unlock:
	rcu_read_unlock();
	dev_put(netdev);
	nfp_flower_cmsg_warn(app, "Requested IPv6 route not found.\n");
}