diff options
Diffstat (limited to 'drivers/rpmsg/virtio_rpmsg_bus.c')
| -rw-r--r-- | drivers/rpmsg/virtio_rpmsg_bus.c | 57 | 
1 files changed, 51 insertions, 6 deletions
diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c index 75506ec2840..f56c8ba3a86 100644 --- a/drivers/rpmsg/virtio_rpmsg_bus.c +++ b/drivers/rpmsg/virtio_rpmsg_bus.c @@ -188,6 +188,26 @@ static int rpmsg_uevent(struct device *dev, struct kobj_uevent_env *env)  					rpdev->id.name);  } +/** + * __ept_release() - deallocate an rpmsg endpoint + * @kref: the ept's reference count + * + * This function deallocates an ept, and is invoked when its @kref refcount + * drops to zero. + * + * Never invoke this function directly! + */ +static void __ept_release(struct kref *kref) +{ +	struct rpmsg_endpoint *ept = container_of(kref, struct rpmsg_endpoint, +						  refcount); +	/* +	 * At this point no one holds a reference to ept anymore, +	 * so we can directly free it +	 */ +	kfree(ept); +} +  /* for more info, see below documentation of rpmsg_create_ept() */  static struct rpmsg_endpoint *__rpmsg_create_ept(struct virtproc_info *vrp,  		struct rpmsg_channel *rpdev, rpmsg_rx_cb_t cb, @@ -206,6 +226,9 @@ static struct rpmsg_endpoint *__rpmsg_create_ept(struct virtproc_info *vrp,  		return NULL;  	} +	kref_init(&ept->refcount); +	mutex_init(&ept->cb_lock); +  	ept->rpdev = rpdev;  	ept->cb = cb;  	ept->priv = priv; @@ -238,7 +261,7 @@ rem_idr:  	idr_remove(&vrp->endpoints, request);  free_ept:  	mutex_unlock(&vrp->endpoints_lock); -	kfree(ept); +	kref_put(&ept->refcount, __ept_release);  	return NULL;  } @@ -302,11 +325,17 @@ EXPORT_SYMBOL(rpmsg_create_ept);  static void  __rpmsg_destroy_ept(struct virtproc_info *vrp, struct rpmsg_endpoint *ept)  { +	/* make sure new inbound messages can't find this ept anymore */  	mutex_lock(&vrp->endpoints_lock);  	idr_remove(&vrp->endpoints, ept->addr);  	mutex_unlock(&vrp->endpoints_lock); -	kfree(ept); +	/* make sure in-flight inbound messages won't invoke cb anymore */ +	mutex_lock(&ept->cb_lock); +	ept->cb = NULL; +	mutex_unlock(&ept->cb_lock); + +	kref_put(&ept->refcount, __ept_release);  }  /** @@ -790,12 +819,28 @@ static void rpmsg_recv_done(struct virtqueue *rvq)  	/* use the dst addr to fetch the callback of the appropriate user */  	mutex_lock(&vrp->endpoints_lock); +  	ept = idr_find(&vrp->endpoints, msg->dst); + +	/* let's make sure no one deallocates ept while we use it */ +	if (ept) +		kref_get(&ept->refcount); +  	mutex_unlock(&vrp->endpoints_lock); -	if (ept && ept->cb) -		ept->cb(ept->rpdev, msg->data, msg->len, ept->priv, msg->src); -	else +	if (ept) { +		/* make sure ept->cb doesn't go away while we use it */ +		mutex_lock(&ept->cb_lock); + +		if (ept->cb) +			ept->cb(ept->rpdev, msg->data, msg->len, ept->priv, +				msg->src); + +		mutex_unlock(&ept->cb_lock); + +		/* farewell, ept, we don't need you anymore */ +		kref_put(&ept->refcount, __ept_release); +	} else  		dev_warn(dev, "msg received with no recepient\n");  	/* publish the real size of the buffer */ @@ -1040,7 +1085,7 @@ static int __init rpmsg_init(void)  	return ret;  } -module_init(rpmsg_init); +subsys_initcall(rpmsg_init);  static void __exit rpmsg_fini(void)  {  |