On Sat, 5 Dec 2015, Steinar H. Gunderson wrote:

> On Sat, Dec 05, 2015 at 12:36:51PM -0500, Alan Stern wrote:
> > I meant that this "if" statement should test only uurb_start.  If the 
> > test succeeds, a nested "if" statement should test buffer_length and 
> > return an error if it is too big.
> > 
> > That's as opposed to the way you have it now, where if buffer_length is 
> > too big you return NULL.  The system would then try to treat the 
> > coherent memory as a normal memory buffer, which may not be a good 
> > idea.
> 
> OK. Why is it a problem to treat it as a normal memory buffer? (I understand
> it would be slower, of course.)

To tell the truth, I'm not sure whether it would be a problem or not.  
That's why I said it "may" not be a good idea.

Mixing the usages would mean mapping the memory region for normal
streaming DMA when it might already have been mapped for coherent DMA.  
Maybe that's okay; I don't know.  It seems safest to avoid the issue.  
Besides, if the user program went to the trouble of mmap'ing a memory
region and then ...:

        ... tried to present a USB buffer that overflowed the end of 
        the region, we should point out the error.

        ... created a control or interrupt transfer with its USB buffer
        in the region, we should honor the program's request and
        perform a zerocopy I/O operation.

> > You don't really need to do it earlier.  An unnecessary calculation of
> > num_sgs won't hurt.
> 
> Is it unnecessary? I thought the test was really to force num_sgs==0 for the
> DMA case, not to save a little arithmetic.

Well, if you calculate num_sgs and then force it to 0 anyway, the 
calculation was unnecessary, right?

> > Comments below.  I'll skip much of the patch because it looks pretty
> > good.
> 
> Good, I guess we're finally converting on something acceptable to all parties 
> :-)

Definitely.

BTW, in the future, could you put your patches in the body of the
emails instead of making them attachments?  That's how we normally do 
things on this mailing list; it makes replying and commenting on 
patches easier.


> +static void dec_usb_memory_use_count(struct usb_memory *usbm, int *count)
> +{
> +     struct usb_dev_state *ps = usbm->ps;
> +     unsigned long flags;
> +
> +     spin_lock_irqsave(&ps->lock, flags);
> +     --*count;
> +     if (usbm->urb_use_count == 0 && usbm->vma_use_count == 0) {
> +             list_del_init(&usbm->memlist);

This can be list_del(), since you're about to deallocate usbm.

> +             spin_unlock_irqrestore(&ps->lock, flags);
> +
> +             usb_free_coherent(ps->dev, usbm->size, usbm->mem,
> +                             usbm->dma_handle);
> +             usbfs_decrease_memory_usage(
> +                     usbm->size + sizeof(struct usb_memory));
> +             kfree(usbm);
> +     } else {
> +             spin_unlock_irqrestore(&ps->lock, flags);
> +     }
> +}

...

> +static struct usb_memory *
> +find_memory_area(struct usb_dev_state *ps, const struct usbdevfs_urb *uurb)
> +{
> +     struct usb_memory *usbm = NULL, *iter;
> +     unsigned long flags;
> +     unsigned long uurb_start = (unsigned long)uurb->buffer;
> +
> +     spin_lock_irqsave(&ps->lock, flags);
> +     list_for_each_entry(iter, &ps->memory_list, memlist) {
> +             if (uurb_start >= iter->vm_start &&
> +                             uurb_start < iter->vm_start + iter->size) {
> +                     if (uurb->buffer_length > iter->vm_start + iter->size -
> +                                     uurb_start) {
> +                             usbm = ERR_PTR(-EINVAL);
> +                     } else {
> +                             usbm = iter;
> +                             usbm->urb_use_count++;
> +                             break;
> +                     }

The "break" belongs here, not where it is.

> +             }
> +     }
> +     spin_unlock_irqrestore(&ps->lock, flags);
> +     return usbm;
> +}
> +
>  static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb 
> *uurb,
>                       struct usbdevfs_iso_packet_desc __user *iso_frame_desc,
>                       void __user *arg)
> @@ -1372,9 +1525,17 @@ static int proc_do_submiturb(struct usb_dev_state *ps, 
> struct usbdevfs_urb *uurb
>                       uurb->type = USBDEVFS_URB_TYPE_INTERRUPT;
>                       goto interrupt_urb;
>               }
> -             num_sgs = DIV_ROUND_UP(uurb->buffer_length, USB_SG_SIZE);
> -             if (num_sgs == 1 || num_sgs > ps->dev->bus->sg_tablesize)
> -                     num_sgs = 0;
> +             /* do not use SG buffers when memory mapped segments
> +              * are in use
> +              */
> +             if (as->usbm) {
> +                     num_sgs = DIV_ROUND_UP(uurb->buffer_length,
> +                                     USB_SG_SIZE);
> +                     if (num_sgs == 1 ||
> +                         num_sgs > ps->dev->bus->sg_tablesize) {
> +                             num_sgs = 0;
> +                     }
> +             }

No, no.  Leave this part of the code unchanged.  as hasn't even been
allocated yet.

>               if (ep->streams)
>                       stream_id = uurb->stream_id;
>               break;
> @@ -1445,10 +1606,15 @@ static int proc_do_submiturb(struct usb_dev_state 
> *ps, struct usbdevfs_urb *uurb
>       if (ret)
>               goto error;
>       as->mem_usage = u;
> +     as->usbm = find_memory_area(ps, uurb);
> +     if (IS_ERR_VALUE(as->usbm)) {
> +             ret = PTR_ERR(as->usbm);
> +             goto error;
> +     }

As pointed out repeatedly by the kbuild test robot, this should be
IS_ERR, not IS_ERR_VALUE.  Also, you need to set as->usbm back to NULL 
before jumping.

At this point, set num_sgs to 0 if as->usbm is non-NULL.  Actually,
now that I look at the code in context, this whole section needs to go
up a little bit, before before the calculation of u.  If num_sgs is
going to be forced to 0, we want to do it before the memory usage is
computed.

>       if (num_sgs) {
>               as->urb->sg = kmalloc(num_sgs * sizeof(struct scatterlist),
> -                                   GFP_KERNEL);
> +                             GFP_KERNEL);

Not relevant to the patch.

>               if (!as->urb->sg) {
>                       ret = -ENOMEM;
>                       goto error;
> @@ -1476,21 +1642,26 @@ static int proc_do_submiturb(struct usb_dev_state 
> *ps, struct usbdevfs_urb *uurb
>                       totlen -= u;
>               }
>       } else if (uurb->buffer_length > 0) {
> -             as->urb->transfer_buffer = kmalloc(uurb->buffer_length,
> +             if (as->usbm) {
> +                     as->urb->transfer_buffer = as->usbm->mem;
> +             } else {
> +                     as->urb->transfer_buffer = kmalloc(uurb->buffer_length,
>                               GFP_KERNEL);
> +             }
>               if (!as->urb->transfer_buffer) {
>                       ret = -ENOMEM;
>                       goto error;
>               }

This test may as well be part of the "else" clause.  We know
as->usbm->mem is always a valid address.

>  
> -             if (!is_in) {
> +             if (!is_in && as->usbm == NULL) {
>                       if (copy_from_user(as->urb->transfer_buffer,
>                                          uurb->buffer,
>                                          uurb->buffer_length)) {
>                               ret = -EFAULT;
>                               goto error;
>                       }
> -             } else if (uurb->type == USBDEVFS_URB_TYPE_ISO) {
> +             } else if (uurb->type == USBDEVFS_URB_TYPE_ISO &&
> +                             as->usbm == NULL) {

My preferred style for avoiding these repeated tests goes like this:

                if (as->usbm) {
                        ;       /* Transfer buffer is okay as it is */
                } else if (!is_in) { ...

You don't have to copy my style, though.

>                       /*
>                        * Isochronous input data may end up being
>                        * discontiguous if some of the packets are short.
> @@ -1545,10 +1716,18 @@ static int proc_do_submiturb(struct usb_dev_state 
> *ps, struct usbdevfs_urb *uurb
>       isopkt = NULL;
>       as->ps = ps;
>       as->userurb = arg;
> -     if (is_in && uurb->buffer_length > 0)
> +     if (is_in && uurb->buffer_length > 0 && as->usbm == NULL) {
>               as->userbuffer = uurb->buffer;
> -     else
> +     } else {
>               as->userbuffer = NULL;
> +     }

The braces aren't needed.  In fact, the NULL assignment isn't needed
either, since as was allocated by kzalloc.

> +     if (as->usbm != NULL) {
> +             unsigned long uurb_start = (unsigned long)uurb->buffer;
> +
> +             as->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
> +             as->urb->transfer_dma = as->usbm->dma_handle +
> +                             (uurb_start - as->usbm->vm_start);

You also should add (uurb_start - as->usbm->vm_start) to
as->urb->transfer_buffer.  Or if you want, you can make that
adjustment where as->urb->transfer_buffer is set originally.

Everything else looks okay.

Alan Stern

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to