gs_start_io() and gs_close()

David Brownell david-b at pacbell.net
Mon Aug 30 23:45:01 EDT 2010


As I mentioned, I'd like to see a few
Tested-By: reports confirming this
patch works, before this gets into
Greg's merge queue ...

So -- can folk using the serial gadget code
please give this a whirl and see if the patch
behaves (or instead makes trouble).




--- On Mon, 8/30/10, Jim Sung <jsung at syncadence.com> wrote:

> From: Jim Sung <jsung at syncadence.com>
> Subject: Re: gs_start_io() and gs_close()
> To: "David Brownell" <david-b at pacbell.net>
> Cc: "Igor Grinberg" <grinberg at compulab.co.il>, linux-usb at vger.kernel.org, "linux-arm-kernel" <linux-arm-kernel at lists.infradead.org>
> Date: Monday, August 30, 2010, 6:30 PM
> Hi Dave:
> 
> >>>    seems wrong.
> > 
> > 
> > Does it cause an actual problem though?  Back
> when
> 
> The short answer is "yes", but not in a way that is easily
> noticeable.
> 
> > that code was written and submitted, ISTR doing a
> > leak analysis and finding none on start_io() paths
> > or their cleanup siblings.  Did something change
> 
> OK, the USB gadget serial driver actually has a couple of
> problems.  On
> gs_open(), it always allocates and queues an additional
> QUEUE_SIZE (16)
> worth of requests, so with a loop like this:
> 
>     i=1 ; while echo $i > /dev/ttyGS0 ; do let
> i++ ; done
> 
> eventually we run into OOM (Out of Memory).
> 
> Technically, it is not a leak as everything gets freed up
> when the USB
> connection is broken, but not on gs_close().
> 
> With a USB device/gadget controller driver that has limited
> resources
> (e.g., Marvell has a this MAX_XDS_FOR_TR_CALLS of 64 for
> transmit and
> receive), so even after 4
> 
>     stty -F /dev/ttyGS0
> 
> we cannot transmit anymore.  We can still receive (not
> necessarily
> reliably) as now we have 16 * 4 = 64 descriptors/buffers
> ready, but the
> device is otherwise not usable.
> 
> Got the
> 0001-pxa168-Dequeue-all-pending-USB-Req-s-submitted-to-U.patch
> from Marvell on 8-27-10, but it still has problems, so I'm
> not going to
> post it here.
> 
> > since then?
> 
> I don't believe so.
> 
> > I don't recall problems being reported in this area
> > over the past several years, either...
> 
> My take is that unless you explicitly look for it, you are
> probably not
> going to notice this.
> 
> Anyway, I tried not to change too much and to follow the
> basic design,
> but you have to be the judge of that.  I ran through a
> handful of tests,
> and it seemed to behave much better now.  Here is the
> patch (against
> 2.6.28), and you can decide whatever you want to do with
> it.
> 
> Jim
> 
> Index: drivers/usb/gadget/u_serial.c
> ===================================================================
> --- drivers/usb/gadget/u_serial.c   
>    (revision 10419)
> +++ drivers/usb/gadget/u_serial.c   
>    (working copy)
> @@ -103,11 +103,15 @@
>         wait_queue_head_t   
>    close_wait;     /*
> wait for last close */
> 
>         struct list_head   
>     read_pool;
> +       int read_started;
> +       int read_allocated;
>         struct list_head   
>     read_queue;
>         unsigned     
>           n_read;
>         struct
> tasklet_struct   push;
> 
>         struct list_head   
>     write_pool;
> +       int write_started;
> +       int write_allocated;
>         struct gs_buf   
>        port_write_buf;
>         wait_queue_head_t   
>    drain_wait;     /*
> wait while writes drain */
> 
> @@ -361,6 +365,9 @@
>                
> struct usb_request      *req;
>                
> int               
>      len;
> 
> +           
>    if (port->write_started >=
> QUEUE_SIZE)
> +               
>        break;
> +
>                 req
> = list_entry(pool->next, struct usb_request, list);
>                 len
> = gs_send_packet(port, req->buf, in->maxpacket);
>                 if
> (len == 0) {
> @@ -394,6 +401,8 @@
>                
>         break;
>                 }
> 
> +           
>    port->write_started++;
> +
>                 /*
> abort immediately after disconnect */
>                 if
> (!port->port_usb)
>                
>         break;
> @@ -415,7 +424,6 @@
>  {
>         struct list_head   
>     *pool = &port->read_pool;
>         struct usb_ep   
>        *out =
> port->port_usb->out;
> -       unsigned   
>             started = 0;
> 
>         while (!list_empty(pool)) {
>                
> struct usb_request      *req;
> @@ -427,6 +435,9 @@
>                 if
> (!tty)
>                
>         break;
> 
> +           
>    if (port->read_started >=
> QUEUE_SIZE)
> +               
>        break;
> +
>                 req
> = list_entry(pool->next, struct usb_request, list);
>                
> list_del(&req->list);
>                
> req->length = out->maxpacket;
> @@ -444,13 +455,13 @@
>                
>         list_add(&req->list,
> pool);
>                
>         break;
>                 }
> -           
>    started++;
> +           
>    port->read_started++;
> 
>                 /*
> abort immediately after disconnect */
>                 if
> (!port->port_usb)
>                
>         break;
>         }
> -       return started;
> +       return
> port->read_started;
>  }
> 
>  /*
> @@ -532,6 +543,7 @@
>                 }
>  recycle:
>                
> list_move(&req->list, &port->read_pool);
> +           
>    port->read_started--;
>         }
> 
>         /* Push from tty to ldisc; this
> is immediate with low_latency, and
> @@ -590,6 +602,7 @@
> 
>        
> spin_lock(&port->port_lock);
>         list_add(&req->list,
> &port->write_pool);
> +       port->write_started--;
> 
>         switch (req->status) {
>         default:
> @@ -611,7 +624,7 @@
>        
> spin_unlock(&port->port_lock);
>  }
> 
> -static void gs_free_requests(struct usb_ep *ep, struct
> list_head *head)
> +static void gs_free_requests(struct usb_ep *ep, struct
> list_head *head, int *allocated)
>  {
>         struct usb_request   
>   *req;
> 
> @@ -619,25 +632,30 @@
>                 req
> = list_entry(head->next, struct usb_request, list);
>                
> list_del(&req->list);
>                
> gs_free_req(ep, req);
> +           
>    if (allocated)
> +               
>        (*allocated)--;
>         }
>  }
> 
>  static int gs_alloc_requests(struct usb_ep *ep, struct
> list_head *head,
> -           
>    void (*fn)(struct usb_ep *, struct
> usb_request *))
> +           
>    void (*fn)(struct usb_ep *, struct
> usb_request *), int *allocated)
>  {
>         int       
>              i;
>         struct usb_request   
>   *req;
> +       int n = allocated ?
> QUEUE_SIZE - *allocated : QUEUE_SIZE;
> 
>         /* Pre-allocate up to
> QUEUE_SIZE transfers, but if we can't
>          * do quite that many
> this time, don't fail ... we just won't
>          * be as speedy as we
> might otherwise be.
>          */
> -       for (i = 0; i <
> QUEUE_SIZE; i++) {
> +       for (i = 0; i < n; i++)
> {
>                 req
> = gs_alloc_req(ep, ep->maxpacket, GFP_ATOMIC);
>                 if
> (!req)
>                
>         return list_empty(head) ?
> -ENOMEM : 0;
>                
> req->complete = fn;
>                
> list_add_tail(&req->list, head);
> +        if (allocated)
> +            (*allocated)++;
>         }
>         return 0;
>  }
> @@ -664,14 +682,14 @@
>          * configurations may
> use different endpoints with a given port;
>          * and high speed vs
> full speed changes packet sizes too.
>          */
> -       status =
> gs_alloc_requests(ep, head, gs_read_complete);
> +       status =
> gs_alloc_requests(ep, head, gs_read_complete,
> &port->read_allocated);
>         if (status)
>                
> return status;
> 
>         status =
> gs_alloc_requests(port->port_usb->in,
> &port->write_pool,
> -               
>        gs_write_complete);
> +               
>        gs_write_complete,
> &port->write_allocated);
>         if (status) {
> -           
>    gs_free_requests(ep, head);
> +           
>    gs_free_requests(ep, head,
> &port->read_allocated);
>                
> return status;
>         }
> 
> @@ -683,8 +701,8 @@
>         if (started) {
>                
> tty_wakeup(port->port_tty);
>         } else {
> -           
>    gs_free_requests(ep, head);
> -           
>    gs_free_requests(port->port_usb->in,
> &port->write_pool);
> +           
>    gs_free_requests(ep, head,
> &port->read_allocated);
> +           
>    gs_free_requests(port->port_usb->in,
> &port->write_pool, &port->write_allocated);
>                
> status = -EIO;
>         }
> 
> @@ -1323,8 +1341,11 @@
>        
> spin_lock_irqsave(&port->port_lock, flags);
>         if (port->open_count == 0
> && !port->openclose)
>                
> gs_buf_free(&port->port_write_buf);
> -   
>    gs_free_requests(gser->out,
> &port->read_pool);
> -   
>    gs_free_requests(gser->out,
> &port->read_queue);
> -   
>    gs_free_requests(gser->in,
> &port->write_pool);
> +   
>    gs_free_requests(gser->out,
> &port->read_pool, NULL);
> +   
>    gs_free_requests(gser->out,
> &port->read_queue, NULL);
> +   
>    gs_free_requests(gser->in,
> &port->write_pool, NULL);
> +
> +       port->read_allocated =
> port->read_started = port->write_allocated =
> port->write_started = 0;
> +
>        
> spin_unlock_irqrestore(&port->port_lock, flags);
>  }
> 




More information about the linux-arm-kernel mailing list