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

Commit 0a7606c1 authored by David S. Miller's avatar David S. Miller
Browse files

[NET]: Fix race between poll_napi() and net_rx_action()



netpoll_poll_lock() synchronizes the ->poll() invocation
code paths, but once we have the lock we have to make
sure that NAPI_STATE_SCHED is still set.  Otherwise we
get:

	cpu 0			cpu 1

	net_rx_action()		poll_napi()
	netpoll_poll_lock()	... spin on ->poll_lock
	->poll()
	  netif_rx_complete
	netpoll_poll_unlock()	acquire ->poll_lock()
				->poll()
				 netif_rx_complete()
				 CRASH

Based upon a bug report from Tina Yang.

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent b0a713e9
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -2172,6 +2172,14 @@ static void net_rx_action(struct softirq_action *h)

		weight = n->weight;

		/* This NAPI_STATE_SCHED test is for avoiding a race
		 * with netpoll's poll_napi().  Only the entity which
		 * obtains the lock and sees NAPI_STATE_SCHED set will
		 * actually make the ->poll() call.  Therefore we avoid
		 * accidently calling ->poll() when NAPI is not scheduled.
		 */
		work = 0;
		if (test_bit(NAPI_STATE_SCHED, &n->state))
			work = n->poll(n, weight);

		WARN_ON_ONCE(work > weight);
+28 −9
Original line number Diff line number Diff line
@@ -116,24 +116,43 @@ static __sum16 checksum_udp(struct sk_buff *skb, struct udphdr *uh,
 * network adapter, forcing superfluous retries and possibly timeouts.
 * Thus, we set our budget to greater than 1.
 */
static void poll_napi(struct netpoll *np)
static int poll_one_napi(struct netpoll_info *npinfo,
			 struct napi_struct *napi, int budget)
{
	struct netpoll_info *npinfo = np->dev->npinfo;
	struct napi_struct *napi;
	int budget = 16;
	int work;

	/* net_rx_action's ->poll() invocations and our's are
	 * synchronized by this test which is only made while
	 * holding the napi->poll_lock.
	 */
	if (!test_bit(NAPI_STATE_SCHED, &napi->state))
		return budget;

	list_for_each_entry(napi, &np->dev->napi_list, dev_list) {
		if (test_bit(NAPI_STATE_SCHED, &napi->state) &&
		    napi->poll_owner != smp_processor_id() &&
		    spin_trylock(&napi->poll_lock)) {
	npinfo->rx_flags |= NETPOLL_RX_DROP;
	atomic_inc(&trapped);

			napi->poll(napi, budget);
	work = napi->poll(napi, budget);

	atomic_dec(&trapped);
	npinfo->rx_flags &= ~NETPOLL_RX_DROP;

	return budget - work;
}

static void poll_napi(struct netpoll *np)
{
	struct netpoll_info *npinfo = np->dev->npinfo;
	struct napi_struct *napi;
	int budget = 16;

	list_for_each_entry(napi, &np->dev->napi_list, dev_list) {
		if (napi->poll_owner != smp_processor_id() &&
		    spin_trylock(&napi->poll_lock)) {
			budget = poll_one_napi(npinfo, napi, budget);
			spin_unlock(&napi->poll_lock);

			if (!budget)
				break;
		}
	}
}