diff options
Diffstat (limited to 'drivers/infiniband/core/addr.c')
| -rw-r--r-- | drivers/infiniband/core/addr.c | 108 | 
1 files changed, 82 insertions, 26 deletions
diff --git a/drivers/infiniband/core/addr.c b/drivers/infiniband/core/addr.c index a6cb379a4ebc..437522ca97b4 100644 --- a/drivers/infiniband/core/addr.c +++ b/drivers/infiniband/core/addr.c @@ -61,6 +61,7 @@ struct addr_req {  	void (*callback)(int status, struct sockaddr *src_addr,  			 struct rdma_dev_addr *addr, void *context);  	unsigned long timeout; +	struct delayed_work work;  	int status;  	u32 seq;  }; @@ -268,6 +269,7 @@ int rdma_translate_ip(const struct sockaddr *addr,  			return ret;  		ret = rdma_copy_addr(dev_addr, dev, NULL); +		dev_addr->bound_dev_if = dev->ifindex;  		if (vlan_id)  			*vlan_id = rdma_vlan_dev_vlan_id(dev);  		dev_put(dev); @@ -280,6 +282,7 @@ int rdma_translate_ip(const struct sockaddr *addr,  					  &((const struct sockaddr_in6 *)addr)->sin6_addr,  					  dev, 1)) {  				ret = rdma_copy_addr(dev_addr, dev, NULL); +				dev_addr->bound_dev_if = dev->ifindex;  				if (vlan_id)  					*vlan_id = rdma_vlan_dev_vlan_id(dev);  				break; @@ -293,7 +296,7 @@ int rdma_translate_ip(const struct sockaddr *addr,  }  EXPORT_SYMBOL(rdma_translate_ip); -static void set_timeout(unsigned long time) +static void set_timeout(struct delayed_work *delayed_work, unsigned long time)  {  	unsigned long delay; @@ -301,7 +304,7 @@ static void set_timeout(unsigned long time)  	if ((long)delay < 0)  		delay = 0; -	mod_delayed_work(addr_wq, &work, delay); +	mod_delayed_work(addr_wq, delayed_work, delay);  }  static void queue_req(struct addr_req *req) @@ -316,8 +319,7 @@ static void queue_req(struct addr_req *req)  	list_add(&req->list, &temp_req->list); -	if (req_list.next == &req->list) -		set_timeout(req->timeout); +	set_timeout(&req->work, req->timeout);  	mutex_unlock(&lock);  } @@ -405,10 +407,10 @@ static int addr4_resolve(struct sockaddr_in *src_in,  	fl4.saddr = src_ip;  	fl4.flowi4_oif = addr->bound_dev_if;  	rt = ip_route_output_key(addr->net, &fl4); -	if (IS_ERR(rt)) { -		ret = PTR_ERR(rt); -		goto out; -	} +	ret = PTR_ERR_OR_ZERO(rt); +	if (ret) +		return ret; +  	src_in->sin_family = AF_INET;  	src_in->sin_addr.s_addr = fl4.saddr; @@ -423,8 +425,6 @@ static int addr4_resolve(struct sockaddr_in *src_in,  	*prt = rt;  	return 0; -out: -	return ret;  }  #if IS_ENABLED(CONFIG_IPV6) @@ -509,6 +509,11 @@ static int addr_resolve(struct sockaddr *src_in,  	struct dst_entry *dst;  	int ret; +	if (!addr->net) { +		pr_warn_ratelimited("%s: missing namespace\n", __func__); +		return -EINVAL; +	} +  	if (src_in->sa_family == AF_INET) {  		struct rtable *rt = NULL;  		const struct sockaddr_in *dst_in4 = @@ -522,8 +527,12 @@ static int addr_resolve(struct sockaddr *src_in,  		if (resolve_neigh)  			ret = addr_resolve_neigh(&rt->dst, dst_in, addr, seq); -		ndev = rt->dst.dev; -		dev_hold(ndev); +		if (addr->bound_dev_if) { +			ndev = dev_get_by_index(addr->net, addr->bound_dev_if); +		} else { +			ndev = rt->dst.dev; +			dev_hold(ndev); +		}  		ip_rt_put(rt);  	} else { @@ -539,19 +548,63 @@ static int addr_resolve(struct sockaddr *src_in,  		if (resolve_neigh)  			ret = addr_resolve_neigh(dst, dst_in, addr, seq); -		ndev = dst->dev; -		dev_hold(ndev); +		if (addr->bound_dev_if) { +			ndev = dev_get_by_index(addr->net, addr->bound_dev_if); +		} else { +			ndev = dst->dev; +			dev_hold(ndev); +		}  		dst_release(dst);  	} -	addr->bound_dev_if = ndev->ifindex; -	addr->net = dev_net(ndev); +	if (ndev->flags & IFF_LOOPBACK) { +		ret = rdma_translate_ip(dst_in, addr, NULL); +		/* +		 * Put the loopback device and get the translated +		 * device instead. +		 */ +		dev_put(ndev); +		ndev = dev_get_by_index(addr->net, addr->bound_dev_if); +	} else { +		addr->bound_dev_if = ndev->ifindex; +	}  	dev_put(ndev);  	return ret;  } +static void process_one_req(struct work_struct *_work) +{ +	struct addr_req *req; +	struct sockaddr *src_in, *dst_in; + +	mutex_lock(&lock); +	req = container_of(_work, struct addr_req, work.work); + +	if (req->status == -ENODATA) { +		src_in = (struct sockaddr *)&req->src_addr; +		dst_in = (struct sockaddr *)&req->dst_addr; +		req->status = addr_resolve(src_in, dst_in, req->addr, +					   true, req->seq); +		if (req->status && time_after_eq(jiffies, req->timeout)) { +			req->status = -ETIMEDOUT; +		} else if (req->status == -ENODATA) { +			/* requeue the work for retrying again */ +			set_timeout(&req->work, req->timeout); +			mutex_unlock(&lock); +			return; +		} +	} +	list_del(&req->list); +	mutex_unlock(&lock); + +	req->callback(req->status, (struct sockaddr *)&req->src_addr, +		req->addr, req->context); +	put_client(req->client); +	kfree(req); +} +  static void process_req(struct work_struct *work)  {  	struct addr_req *req, *temp_req; @@ -569,20 +622,23 @@ static void process_req(struct work_struct *work)  						   true, req->seq);  			if (req->status && time_after_eq(jiffies, req->timeout))  				req->status = -ETIMEDOUT; -			else if (req->status == -ENODATA) +			else if (req->status == -ENODATA) { +				set_timeout(&req->work, req->timeout);  				continue; +			}  		}  		list_move_tail(&req->list, &done_list);  	} -	if (!list_empty(&req_list)) { -		req = list_entry(req_list.next, struct addr_req, list); -		set_timeout(req->timeout); -	}  	mutex_unlock(&lock);  	list_for_each_entry_safe(req, temp_req, &done_list, list) {  		list_del(&req->list); +		/* It is safe to cancel other work items from this work item +		 * because at a time there can be only one work item running +		 * with this single threaded work queue. +		 */ +		cancel_delayed_work(&req->work);  		req->callback(req->status, (struct sockaddr *) &req->src_addr,  			req->addr, req->context);  		put_client(req->client); @@ -625,6 +681,7 @@ int rdma_resolve_ip(struct rdma_addr_client *client,  	req->context = context;  	req->client = client;  	atomic_inc(&client->refcount); +	INIT_DELAYED_WORK(&req->work, process_one_req);  	req->seq = (u32)atomic_inc_return(&ib_nl_addr_request_seq);  	req->status = addr_resolve(src_in, dst_in, addr, true, req->seq); @@ -679,7 +736,7 @@ void rdma_addr_cancel(struct rdma_dev_addr *addr)  			req->status = -ECANCELED;  			req->timeout = jiffies;  			list_move(&req->list, &req_list); -			set_timeout(req->timeout); +			set_timeout(&req->work, req->timeout);  			break;  		}  	} @@ -785,9 +842,8 @@ static int netevent_callback(struct notifier_block *self, unsigned long event,  	if (event == NETEVENT_NEIGH_UPDATE) {  		struct neighbour *neigh = ctx; -		if (neigh->nud_state & NUD_VALID) { -			set_timeout(jiffies); -		} +		if (neigh->nud_state & NUD_VALID) +			set_timeout(&work, jiffies);  	}  	return 0;  } @@ -798,7 +854,7 @@ static struct notifier_block nb = {  int addr_init(void)  { -	addr_wq = alloc_workqueue("ib_addr", WQ_MEM_RECLAIM, 0); +	addr_wq = alloc_ordered_workqueue("ib_addr", WQ_MEM_RECLAIM);  	if (!addr_wq)  		return -ENOMEM;  | 
