diff options
Diffstat (limited to 'drivers/net/hyperv/netvsc.c')
| -rw-r--r-- | drivers/net/hyperv/netvsc.c | 54 | 
1 files changed, 46 insertions, 8 deletions
diff --git a/drivers/net/hyperv/netvsc.c b/drivers/net/hyperv/netvsc.c index 5d5bd513847f..31c3d77b4733 100644 --- a/drivers/net/hyperv/netvsc.c +++ b/drivers/net/hyperv/netvsc.c @@ -65,6 +65,41 @@ void netvsc_switch_datapath(struct net_device *ndev, bool vf)  			       VM_PKT_DATA_INBAND, 0);  } +/* Worker to setup sub channels on initial setup + * Initial hotplug event occurs in softirq context + * and can't wait for channels. + */ +static void netvsc_subchan_work(struct work_struct *w) +{ +	struct netvsc_device *nvdev = +		container_of(w, struct netvsc_device, subchan_work); +	struct rndis_device *rdev; +	int i, ret; + +	/* Avoid deadlock with device removal already under RTNL */ +	if (!rtnl_trylock()) { +		schedule_work(w); +		return; +	} + +	rdev = nvdev->extension; +	if (rdev) { +		ret = rndis_set_subchannel(rdev->ndev, nvdev); +		if (ret == 0) { +			netif_device_attach(rdev->ndev); +		} else { +			/* fallback to only primary channel */ +			for (i = 1; i < nvdev->num_chn; i++) +				netif_napi_del(&nvdev->chan_table[i].napi); + +			nvdev->max_chn = 1; +			nvdev->num_chn = 1; +		} +	} + +	rtnl_unlock(); +} +  static struct netvsc_device *alloc_net_device(void)  {  	struct netvsc_device *net_device; @@ -81,7 +116,7 @@ static struct netvsc_device *alloc_net_device(void)  	init_completion(&net_device->channel_init_wait);  	init_waitqueue_head(&net_device->subchan_open); -	INIT_WORK(&net_device->subchan_work, rndis_set_subchannel); +	INIT_WORK(&net_device->subchan_work, netvsc_subchan_work);  	return net_device;  } @@ -1239,6 +1274,7 @@ int netvsc_poll(struct napi_struct *napi, int budget)  	struct hv_device *device = netvsc_channel_to_device(channel);  	struct net_device *ndev = hv_get_drvdata(device);  	int work_done = 0; +	int ret;  	/* If starting a new interval */  	if (!nvchan->desc) @@ -1250,16 +1286,18 @@ int netvsc_poll(struct napi_struct *napi, int budget)  		nvchan->desc = hv_pkt_iter_next(channel, nvchan->desc);  	} -	/* If send of pending receive completions suceeded -	 *   and did not exhaust NAPI budget this time -	 *   and not doing busy poll +	/* Send any pending receive completions */ +	ret = send_recv_completions(ndev, net_device, nvchan); + +	/* If it did not exhaust NAPI budget this time +	 *  and not doing busy poll  	 * then re-enable host interrupts -	 *     and reschedule if ring is not empty. +	 *  and reschedule if ring is not empty +	 *   or sending receive completion failed.  	 */ -	if (send_recv_completions(ndev, net_device, nvchan) == 0 && -	    work_done < budget && +	if (work_done < budget &&  	    napi_complete_done(napi, work_done) && -	    hv_end_read(&channel->inbound) && +	    (ret || hv_end_read(&channel->inbound)) &&  	    napi_schedule_prep(napi)) {  		hv_begin_read(&channel->inbound);  		__napi_schedule(napi);  |