From d05b6ec01b8186f847ac9e41098e40858926db40 Mon Sep 17 00:00:00 2001 From: Arvid Brodin Date: Fri, 20 May 2011 00:17:45 +0200 Subject: [PATCH] usb/isp1760: Fix possible unlink problems Use skip map to avoid spurious interrupts from unlinked transfers. Also changes to urb_dequeue() and endpoint_disable() to avoid release of spinlock in uncertain state. Signed-off-by: Arvid Brodin Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/isp1760-hcd.c | 147 ++++++++++++++++++++++++++--------------- 1 file changed, 95 insertions(+), 52 deletions(-) diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 485fc70625a..c9e6e454c62 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -34,7 +34,9 @@ struct isp1760_hcd { u32 hcs_params; spinlock_t lock; struct slotinfo atl_slots[32]; + int atl_done_map; struct slotinfo int_slots[32]; + int int_done_map; struct memory_chunk memory_pool[BLOCKS]; struct list_head controlqhs, bulkqhs, interruptqhs; int active_ptds; @@ -519,9 +521,9 @@ static void isp1760_init_maps(struct usb_hcd *hcd) reg_write32(hcd->regs, HC_INT_PTD_LASTPTD_REG, 0x80000000); reg_write32(hcd->regs, HC_ISO_PTD_LASTPTD_REG, 0x00000001); - reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, 0); - reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, 0); - reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, 0); + reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, 0xffffffff); + reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, 0xffffffff); + reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, 0xffffffff); reg_write32(hcd->regs, HC_BUFFER_STATUS_REG, ATL_BUF_FILL | INT_BUF_FILL); @@ -803,6 +805,8 @@ static void start_bus_transfer(struct usb_hcd *hcd, u32 ptd_offset, int slot, struct isp1760_qh *qh, struct ptd *ptd) { struct isp1760_hcd *priv = hcd_to_priv(hcd); + int skip_map; + WARN_ON((slot < 0) || (slot > 31)); WARN_ON(qtd->length && !qtd->payload_addr); WARN_ON(slots[slot].qtd); @@ -816,6 +820,25 @@ static void start_bus_transfer(struct usb_hcd *hcd, u32 ptd_offset, int slot, interrupt routine may preempt and expects this value. */ ptd_write(hcd->regs, ptd_offset, slot, ptd); priv->active_ptds++; + + /* Make sure done map has not triggered from some unlinked transfer */ + if (ptd_offset == ATL_PTD_OFFSET) { + priv->atl_done_map |= reg_read32(hcd->regs, + HC_ATL_PTD_DONEMAP_REG); + priv->atl_done_map &= ~(1 << qh->slot); + + skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); + skip_map &= ~(1 << qh->slot); + reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, skip_map); + } else { + priv->int_done_map |= reg_read32(hcd->regs, + HC_INT_PTD_DONEMAP_REG); + priv->int_done_map &= ~(1 << qh->slot); + + skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); + skip_map &= ~(1 << qh->slot); + reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, skip_map); + } } static int is_short_bulk(struct isp1760_qtd *qtd) @@ -1152,7 +1175,6 @@ static irqreturn_t isp1760_irq(struct usb_hcd *hcd) irqreturn_t irqret = IRQ_NONE; struct ptd ptd; struct isp1760_qh *qh; - int int_done_map, atl_done_map; int slot; int state; struct slotinfo *slots; @@ -1160,6 +1182,7 @@ static irqreturn_t isp1760_irq(struct usb_hcd *hcd) struct isp1760_qtd *qtd; int modified; static int last_active_ptds; + int int_skip_map, atl_skip_map; spin_lock(&priv->lock); @@ -1171,29 +1194,42 @@ static irqreturn_t isp1760_irq(struct usb_hcd *hcd) goto leave; reg_write32(hcd->regs, HC_INTERRUPT_REG, imask); /* Clear */ - int_done_map = reg_read32(hcd->regs, HC_INT_PTD_DONEMAP_REG); - atl_done_map = reg_read32(hcd->regs, HC_ATL_PTD_DONEMAP_REG); - modified = int_done_map | atl_done_map; + int_skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); + atl_skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); + priv->int_done_map |= reg_read32(hcd->regs, HC_INT_PTD_DONEMAP_REG); + priv->atl_done_map |= reg_read32(hcd->regs, HC_ATL_PTD_DONEMAP_REG); + priv->int_done_map &= ~int_skip_map; + priv->atl_done_map &= ~atl_skip_map; - while (int_done_map || atl_done_map) { - if (int_done_map) { + modified = priv->int_done_map | priv->atl_done_map; + + while (priv->int_done_map || priv->atl_done_map) { + if (priv->int_done_map) { /* INT ptd */ - slot = __ffs(int_done_map); - int_done_map &= ~(1 << slot); + slot = __ffs(priv->int_done_map); + priv->int_done_map &= ~(1 << slot); slots = priv->int_slots; - if (!slots[slot].qh) + /* This should not trigger, and could be removed if + noone have any problems with it triggering: */ + if (!slots[slot].qh) { + WARN_ON(1); continue; + } ptd_offset = INT_PTD_OFFSET; ptd_read(hcd->regs, INT_PTD_OFFSET, slot, &ptd); state = check_int_transfer(hcd, &ptd, slots[slot].qtd->urb); } else { /* ATL ptd */ - slot = __ffs(atl_done_map); - atl_done_map &= ~(1 << slot); + slot = __ffs(priv->atl_done_map); + priv->atl_done_map &= ~(1 << slot); slots = priv->atl_slots; - if (!slots[slot].qh) + /* This should not trigger, and could be removed if + noone have any problems with it triggering: */ + if (!slots[slot].qh) { + WARN_ON(1); continue; + } ptd_offset = ATL_PTD_OFFSET; ptd_read(hcd->regs, ATL_PTD_OFFSET, slot, &ptd); state = check_atl_transfer(hcd, &ptd, @@ -1509,14 +1545,41 @@ out: return retval; } +static void kill_transfer(struct usb_hcd *hcd, struct urb *urb, + struct isp1760_qh *qh) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int skip_map; + + WARN_ON(qh->slot == -1); + + /* We need to forcefully reclaim the slot since some transfers never + return, e.g. interrupt transfers and NAKed bulk transfers. */ + if (usb_pipebulk(urb->pipe)) { + skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); + skip_map |= (1 << qh->slot); + reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, skip_map); + priv->atl_slots[qh->slot].qh = NULL; + priv->atl_slots[qh->slot].qtd = NULL; + } else { + skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); + skip_map |= (1 << qh->slot); + reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, skip_map); + priv->int_slots[qh->slot].qh = NULL; + priv->int_slots[qh->slot].qtd = NULL; + } + + qh->slot = -1; + priv->active_ptds--; +} + static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) { struct isp1760_hcd *priv = hcd_to_priv(hcd); + unsigned long spinflags; struct isp1760_qh *qh; struct isp1760_qtd *qtd; - struct ptd ptd; - unsigned long spinflags; int retval = 0; spin_lock_irqsave(&priv->lock, spinflags); @@ -1527,34 +1590,18 @@ static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, goto out; } - /* We need to forcefully reclaim the slot since some transfers never - return, e.g. interrupt transfers and NAKed bulk transfers. */ - if (qh->slot > -1) { - memset(&ptd, 0, sizeof(ptd)); - if (usb_pipebulk(urb->pipe)) { - priv->atl_slots[qh->slot].qh = NULL; - priv->atl_slots[qh->slot].qtd = NULL; - ptd_write(hcd->regs, ATL_PTD_OFFSET, qh->slot, &ptd); - } else { - priv->int_slots[qh->slot].qh = NULL; - priv->int_slots[qh->slot].qtd = NULL; - ptd_write(hcd->regs, INT_PTD_OFFSET, qh->slot, &ptd); - } - priv->active_ptds--; - qh->slot = -1; - } - - list_for_each_entry(qtd, &qh->qtd_list, qtd_list) { - if (qtd->urb == urb) + list_for_each_entry(qtd, &qh->qtd_list, qtd_list) + if (qtd->urb == urb) { + if (qtd->status == QTD_XFER_STARTED) + kill_transfer(hcd, urb, qh); qtd->status = QTD_RETIRE; - } + } urb->status = status; schedule_ptds(hcd); out: spin_unlock_irqrestore(&priv->lock, spinflags); - return retval; } @@ -1562,32 +1609,28 @@ static void isp1760_endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep) { struct isp1760_hcd *priv = hcd_to_priv(hcd); + unsigned long spinflags; struct isp1760_qh *qh; struct isp1760_qtd *qtd; - unsigned long spinflags; - int do_iter; spin_lock_irqsave(&priv->lock, spinflags); + qh = ep->hcpriv; if (!qh) goto out; - do_iter = !list_empty(&qh->qtd_list); - while (do_iter) { - do_iter = 0; - list_for_each_entry(qtd, &qh->qtd_list, qtd_list) { - if (qtd->urb->ep == ep) { - spin_unlock_irqrestore(&priv->lock, spinflags); - isp1760_urb_dequeue(hcd, qtd->urb, -ECONNRESET); - spin_lock_irqsave(&priv->lock, spinflags); - do_iter = 1; - break; /* Restart iteration */ - } - } + list_for_each_entry(qtd, &qh->qtd_list, qtd_list) { + if (qtd->status == QTD_XFER_STARTED) + kill_transfer(hcd, qtd->urb, qh); + qtd->status = QTD_RETIRE; + qtd->urb->status = -ECONNRESET; } + ep->hcpriv = NULL; /* Cannot free qh here since it will be parsed by schedule_ptds() */ + schedule_ptds(hcd); + out: spin_unlock_irqrestore(&priv->lock, spinflags); } -- 2.11.4.GIT