usb: gadget: gr_udc: Add bounce buffer to handle odd sized OUT requests

This adds a bounce buffer that handles the end of OUT requests where
req.length is not divisible by ep->ep.maxpacket.

Before this, such requests were rejected as the DMA engine cannot
restrict itself to buffers that are smaller than ep->ep.maxpacket.

Signed-off-by: Andreas Larsson <andreas@gaisler.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
This commit is contained in:
Andreas Larsson 2014-09-15 12:32:54 +02:00 коммит произвёл Felipe Balbi
Родитель af54954ad0
Коммит 5b484989a9
2 изменённых файлов: 67 добавлений и 21 удалений

Просмотреть файл

@ -318,8 +318,26 @@ static void gr_finish_request(struct gr_ep *ep, struct gr_request *req,
usb_gadget_unmap_request(&dev->gadget, &req->req, ep->is_in);
gr_free_dma_desc_chain(dev, req);
if (ep->is_in) /* For OUT, actual gets updated bit by bit */
if (ep->is_in) { /* For OUT, req->req.actual gets updated bit by bit */
req->req.actual = req->req.length;
} else if (req->oddlen && req->req.actual > req->evenlen) {
/*
* Copy to user buffer in this case where length was not evenly
* divisible by ep->ep.maxpacket and the last descriptor was
* actually used.
*/
char *buftail = ((char *)req->req.buf + req->evenlen);
memcpy(buftail, ep->tailbuf, req->oddlen);
if (req->req.actual > req->req.length) {
/* We got more data than was requested */
dev_dbg(ep->dev->dev, "Overflow for ep %s\n",
ep->ep.name);
gr_dbgprint_request("OVFL", ep, req);
req->req.status = -EOVERFLOW;
}
}
if (!status) {
if (ep->is_in)
@ -379,6 +397,15 @@ static void gr_start_dma(struct gr_ep *ep)
/* A descriptor should already have been allocated */
BUG_ON(!req->curr_desc);
/*
* The DMA controller can not handle smaller OUT buffers than
* ep->ep.maxpacket. It could lead to buffer overruns if an unexpectedly
* long packet are received. Therefore an internal bounce buffer gets
* used when such a request gets enabled.
*/
if (!ep->is_in && req->oddlen)
req->last_desc->data = ep->tailbuf_paddr;
wmb(); /* Make sure all is settled before handing it over to DMA */
/* Set the descriptor pointer in the hardware */
@ -480,11 +507,11 @@ static int gr_setup_out_desc_list(struct gr_ep *ep, struct gr_request *req,
dma_addr_t start = req->req.dma + bytes_used;
u16 size = min(bytes_left, ep->bytes_per_buffer);
/* Should not happen however - gr_queue stops such lengths */
if (size < ep->bytes_per_buffer)
dev_warn(ep->dev->dev,
"Buffer overrun risk: %u < %u bytes/buffer\n",
size, ep->bytes_per_buffer);
if (size < ep->bytes_per_buffer) {
/* Prepare using bounce buffer */
req->evenlen = req->req.length - bytes_left;
req->oddlen = size;
}
ret = gr_add_dma_desc(ep, req, start, size, gfp_flags);
if (ret)
@ -584,18 +611,6 @@ static int gr_queue(struct gr_ep *ep, struct gr_request *req, gfp_t gfp_flags)
return -EINVAL;
}
/*
* The DMA controller can not handle smaller OUT buffers than
* maxpacket. It could lead to buffer overruns if unexpectedly long
* packet are received.
*/
if (!ep->is_in && (req->req.length % ep->ep.maxpacket) != 0) {
dev_err(dev->dev,
"OUT request length %d is not multiple of maxpacket\n",
req->req.length);
return -EMSGSIZE;
}
if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) {
dev_err(dev->dev, "-ESHUTDOWN");
return -ESHUTDOWN;
@ -1286,8 +1301,8 @@ static int gr_handle_out_ep(struct gr_ep *ep)
if (ctrl & GR_DESC_OUT_CTRL_SE)
req->setup = 1;
if (len < ep->ep.maxpacket || req->req.actual == req->req.length) {
/* Short packet or the expected size - we are done */
if (len < ep->ep.maxpacket || req->req.actual >= req->req.length) {
/* Short packet or >= expected size - we are done */
if ((ep == &dev->epo[0]) && (dev->ep0state == GR_EP0_OSTATUS)) {
/*
@ -2015,6 +2030,11 @@ static int gr_ep_init(struct gr_udc *dev, int num, int is_in, u32 maxplimit)
}
list_add_tail(&ep->ep_list, &dev->ep_list);
ep->tailbuf = dma_alloc_coherent(dev->dev, ep->ep.maxpacket_limit,
&ep->tailbuf_paddr, GFP_ATOMIC);
if (!ep->tailbuf)
return -ENOMEM;
return 0;
}
@ -2067,9 +2087,24 @@ static int gr_udc_init(struct gr_udc *dev)
return 0;
}
static void gr_ep_remove(struct gr_udc *dev, int num, int is_in)
{
struct gr_ep *ep;
if (is_in)
ep = &dev->epi[num];
else
ep = &dev->epo[num];
if (ep->tailbuf)
dma_free_coherent(dev->dev, ep->ep.maxpacket_limit,
ep->tailbuf, ep->tailbuf_paddr);
}
static int gr_remove(struct platform_device *pdev)
{
struct gr_udc *dev = platform_get_drvdata(pdev);
int i;
if (dev->added)
usb_del_gadget_udc(&dev->gadget); /* Shuts everything down */
@ -2084,6 +2119,11 @@ static int gr_remove(struct platform_device *pdev)
gr_free_request(&dev->epi[0].ep, &dev->ep0reqi->req);
gr_free_request(&dev->epo[0].ep, &dev->ep0reqo->req);
for (i = 0; i < dev->nepo; i++)
gr_ep_remove(dev, i, 0);
for (i = 0; i < dev->nepi; i++)
gr_ep_remove(dev, i, 1);
return 0;
}
static int gr_request_irq(struct gr_udc *dev, int irq)
@ -2131,7 +2171,6 @@ static int gr_probe(struct platform_device *pdev)
dev->gadget.name = driver_name;
dev->gadget.max_speed = USB_SPEED_HIGH;
dev->gadget.ops = &gr_ops;
dev->gadget.quirk_ep_out_aligned_size = true;
spin_lock_init(&dev->lock);
dev->regs = regs;

Просмотреть файл

@ -156,6 +156,10 @@ struct gr_ep {
struct list_head queue;
struct list_head ep_list;
/* Bounce buffer for end of "odd" sized OUT requests */
void *tailbuf;
dma_addr_t tailbuf_paddr;
};
struct gr_request {
@ -167,6 +171,9 @@ struct gr_request {
struct gr_dma_desc *curr_desc; /* Current descriptor */
struct gr_dma_desc *last_desc; /* Last in the chain */
u16 evenlen; /* Size of even length head (if oddlen != 0) */
u16 oddlen; /* Size of odd length tail if buffer length is "odd" */
u8 setup; /* Setup packet */
};