Re: [PATCH v6 04/25] iommu: Add a page fault handler

Jean-Philippe Brucker
2020-05-04T16:22:58Z

On Sun, May 03, 2020 at 01:49:01PM +0800, Lu Baolu wrote:
> > +static void iopf_handle_group(struct work_struct *work)
> > +{
> > + struct iopf_group *group;
> > + struct iopf_fault *iopf, *next;
> > + enum iommu_page_response_code status = IOMMU_PAGE_RESP_SUCCESS;
> > +
> > + group = container_of(work, struct iopf_group, work);
> > +
> > + list_for_each_entry_safe(iopf, next, &group->faults, head) {
> > + /*
> > + * For the moment, errors are sticky: don't handle subsequent
> > + * faults in the group if there is an error.
> > + */
> > + if (status == IOMMU_PAGE_RESP_SUCCESS)
> > + status = iopf_handle_single(iopf);
> > +
> > + if (!(iopf->fault.prm.flags &
> > + IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE))
> > + kfree(iopf);
>
> The iopf is freed,but not removed from the list. This will cause wild
> pointer in code.

We free the list with the group below, so this one is fine.

>
> > + }
> > +
> > + iopf_complete_group(group->dev, &group->last_fault, status);
> > + kfree(group);
> > +}
> > +
>
> [...]
>
> > +/**
> > + * iopf_queue_flush_dev - Ensure that all queued faults have been processed
> > + * @dev: the endpoint whose faults need to be flushed.
> > + * @pasid: the PASID affected by this flush
> > + *
> > + * The IOMMU driver calls this before releasing a PASID, to ensure that all
> > + * pending faults for this PASID have been handled, and won't hit the address
> > + * space of the next process that uses this PASID. The driver must make sure
> > + * that no new fault is added to the queue. In particular it must flush its
> > + * low-level queue before calling this function.
> > + *
> > + * Return: 0 on success and <0 on error.
> > + */
> > +int iopf_queue_flush_dev(struct device *dev, int pasid)
> > +{
> > + int ret = 0;
> > + struct iopf_device_param *iopf_param;
> > + struct dev_iommu *param = dev->iommu;
> > +
> > + if (!param)
> > + return -ENODEV;
> > +
> > + mutex_lock(&param->lock);
> > + iopf_param = param->iopf_param;
> > + if (iopf_param)
> > + flush_workqueue(iopf_param->queue->wq);
>
> There may be other pasid iopf in the workqueue. Flush all tasks in
> the workqueue will hurt other pasids. I might lose any context.

Granted this isn't optimal because we don't take the PASID argument into
account (I think I'll remove it, don't know how to use it). But I don't
think it affects other PASIDs, because all flush_workqueue() does is wait
until all faults currently in the worqueue are processed. So it only
blocks the current thread, but nothing is lost.

>
> > + else
> > + ret = -ENODEV;
> > + mutex_unlock(&param->lock);
> > +
> > + return ret;
> > +}
> > +EXPORT_SYMBOL_GPL(iopf_queue_flush_dev);
> > +
> > +/**
> > + * iopf_queue_discard_partial - Remove all pending partial fault
> > + * @queue: the queue whose partial faults need to be discarded
> > + *
> > + * When the hardware queue overflows, last page faults in a group may have been
> > + * lost and the IOMMU driver calls this to discard all partial faults. The
> > + * driver shouldn't be adding new faults to this queue concurrently.
> > + *
> > + * Return: 0 on success and <0 on error.
> > + */
> > +int iopf_queue_discard_partial(struct iopf_queue *queue)
> > +{
> > + struct iopf_fault *iopf, *next;
> > + struct iopf_device_param *iopf_param;
> > +
> > + if (!queue)
> > + return -EINVAL;
> > +
> > + mutex_lock(&queue->lock);
> > + list_for_each_entry(iopf_param, &queue->devices, queue_list) {
> > + list_for_each_entry_safe(iopf, next, &iopf_param->partial, head)
> > + kfree(iopf);
>
> iopf is freed but not removed from the list.

Ouch yes this is wrong, will fix.

>
> > + }
> > + mutex_unlock(&queue->lock);
> > + return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(iopf_queue_discard_partial);
> > +
> > +/**
> > + * iopf_queue_add_device - Add producer to the fault queue
> > + * @queue: IOPF queue
> > + * @dev: device to add
> > + *
> > + * Return: 0 on success and <0 on error.
> > + */
> > +int iopf_queue_add_device(struct iopf_queue *queue, struct device *dev)
> > +{
> > + int ret = -EBUSY;
> > + struct iopf_device_param *iopf_param;
> > + struct dev_iommu *param = dev->iommu;
> > +
> > + if (!param)
> > + return -ENODEV;
> > +
> > + iopf_param = kzalloc(sizeof(*iopf_param), GFP_KERNEL);
> > + if (!iopf_param)
> > + return -ENOMEM;
> > +
> > + INIT_LIST_HEAD(&iopf_param->partial);
> > + iopf_param->queue = queue;
> > + iopf_param->dev = dev;
> > +
> > + mutex_lock(&queue->lock);
> > + mutex_lock(&param->lock);
> > + if (!param->iopf_param) {
> > + list_add(&iopf_param->queue_list, &queue->devices);
> > + param->iopf_param = iopf_param;
> > + ret = 0;
> > + }
> > + mutex_unlock(&param->lock);
> > + mutex_unlock(&queue->lock);
> > +
> > + if (ret)
> > + kfree(iopf_param);
> > +
> > + return ret;
> > +}
> > +EXPORT_SYMBOL_GPL(iopf_queue_add_device);
> > +
> > +/**
> > + * iopf_queue_remove_device - Remove producer from fault queue
> > + * @queue: IOPF queue
> > + * @dev: device to remove
> > + *
> > + * Caller makes sure that no more faults are reported for this device.
> > + *
> > + * Return: 0 on success and <0 on error.
> > + */
> > +int iopf_queue_remove_device(struct iopf_queue *queue, struct device *dev)
> > +{
> > + int ret = 0;
> > + struct iopf_fault *iopf, *next;
> > + struct iopf_device_param *iopf_param;
> > + struct dev_iommu *param = dev->iommu;
> > +
> > + if (!param || !queue)
> > + return -EINVAL;
> > +
> > + mutex_lock(&queue->lock);
> > + mutex_lock(&param->lock);
> > + iopf_param = param->iopf_param;
> > + if (iopf_param && iopf_param->queue == queue) {
> > + list_del(&iopf_param->queue_list);
> > + param->iopf_param = NULL;
> > + } else {
> > + ret = -EINVAL;
> > + }
> > + mutex_unlock(&param->lock);
> > + mutex_unlock(&queue->lock);
> > + if (ret)
> > + return ret;
> > +
> > + /* Just in case some faults are still stuck */
> > + list_for_each_entry_safe(iopf, next, &iopf_param->partial, head)
> > + kfree(iopf);
>
> The same here.

Here is fine, we free the iopf_param below

Thanks,
Jean

>
> > +
> > + kfree(iopf_param);
> > +
> > + return 0;
> > +}
> > +EXPORT_SYMBOL_GPL(iopf_queue_remove_device);