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 <arvid.brodin@enea.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
parent
079cdb0947
commit
d05b6ec01b
|
@ -34,7 +34,9 @@ struct isp1760_hcd {
|
||||||
u32 hcs_params;
|
u32 hcs_params;
|
||||||
spinlock_t lock;
|
spinlock_t lock;
|
||||||
struct slotinfo atl_slots[32];
|
struct slotinfo atl_slots[32];
|
||||||
|
int atl_done_map;
|
||||||
struct slotinfo int_slots[32];
|
struct slotinfo int_slots[32];
|
||||||
|
int int_done_map;
|
||||||
struct memory_chunk memory_pool[BLOCKS];
|
struct memory_chunk memory_pool[BLOCKS];
|
||||||
struct list_head controlqhs, bulkqhs, interruptqhs;
|
struct list_head controlqhs, bulkqhs, interruptqhs;
|
||||||
int active_ptds;
|
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_INT_PTD_LASTPTD_REG, 0x80000000);
|
||||||
reg_write32(hcd->regs, HC_ISO_PTD_LASTPTD_REG, 0x00000001);
|
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_ATL_PTD_SKIPMAP_REG, 0xffffffff);
|
||||||
reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, 0);
|
reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, 0xffffffff);
|
||||||
reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, 0);
|
reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, 0xffffffff);
|
||||||
|
|
||||||
reg_write32(hcd->regs, HC_BUFFER_STATUS_REG,
|
reg_write32(hcd->regs, HC_BUFFER_STATUS_REG,
|
||||||
ATL_BUF_FILL | INT_BUF_FILL);
|
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_qh *qh, struct ptd *ptd)
|
||||||
{
|
{
|
||||||
struct isp1760_hcd *priv = hcd_to_priv(hcd);
|
struct isp1760_hcd *priv = hcd_to_priv(hcd);
|
||||||
|
int skip_map;
|
||||||
|
|
||||||
WARN_ON((slot < 0) || (slot > 31));
|
WARN_ON((slot < 0) || (slot > 31));
|
||||||
WARN_ON(qtd->length && !qtd->payload_addr);
|
WARN_ON(qtd->length && !qtd->payload_addr);
|
||||||
WARN_ON(slots[slot].qtd);
|
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. */
|
interrupt routine may preempt and expects this value. */
|
||||||
ptd_write(hcd->regs, ptd_offset, slot, ptd);
|
ptd_write(hcd->regs, ptd_offset, slot, ptd);
|
||||||
priv->active_ptds++;
|
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)
|
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;
|
irqreturn_t irqret = IRQ_NONE;
|
||||||
struct ptd ptd;
|
struct ptd ptd;
|
||||||
struct isp1760_qh *qh;
|
struct isp1760_qh *qh;
|
||||||
int int_done_map, atl_done_map;
|
|
||||||
int slot;
|
int slot;
|
||||||
int state;
|
int state;
|
||||||
struct slotinfo *slots;
|
struct slotinfo *slots;
|
||||||
|
@ -1160,6 +1182,7 @@ static irqreturn_t isp1760_irq(struct usb_hcd *hcd)
|
||||||
struct isp1760_qtd *qtd;
|
struct isp1760_qtd *qtd;
|
||||||
int modified;
|
int modified;
|
||||||
static int last_active_ptds;
|
static int last_active_ptds;
|
||||||
|
int int_skip_map, atl_skip_map;
|
||||||
|
|
||||||
spin_lock(&priv->lock);
|
spin_lock(&priv->lock);
|
||||||
|
|
||||||
|
@ -1171,29 +1194,42 @@ static irqreturn_t isp1760_irq(struct usb_hcd *hcd)
|
||||||
goto leave;
|
goto leave;
|
||||||
reg_write32(hcd->regs, HC_INTERRUPT_REG, imask); /* Clear */
|
reg_write32(hcd->regs, HC_INTERRUPT_REG, imask); /* Clear */
|
||||||
|
|
||||||
int_done_map = reg_read32(hcd->regs, HC_INT_PTD_DONEMAP_REG);
|
int_skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG);
|
||||||
atl_done_map = reg_read32(hcd->regs, HC_ATL_PTD_DONEMAP_REG);
|
atl_skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG);
|
||||||
modified = int_done_map | atl_done_map;
|
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) {
|
modified = priv->int_done_map | priv->atl_done_map;
|
||||||
if (int_done_map) {
|
|
||||||
|
while (priv->int_done_map || priv->atl_done_map) {
|
||||||
|
if (priv->int_done_map) {
|
||||||
/* INT ptd */
|
/* INT ptd */
|
||||||
slot = __ffs(int_done_map);
|
slot = __ffs(priv->int_done_map);
|
||||||
int_done_map &= ~(1 << slot);
|
priv->int_done_map &= ~(1 << slot);
|
||||||
slots = priv->int_slots;
|
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;
|
continue;
|
||||||
|
}
|
||||||
ptd_offset = INT_PTD_OFFSET;
|
ptd_offset = INT_PTD_OFFSET;
|
||||||
ptd_read(hcd->regs, INT_PTD_OFFSET, slot, &ptd);
|
ptd_read(hcd->regs, INT_PTD_OFFSET, slot, &ptd);
|
||||||
state = check_int_transfer(hcd, &ptd,
|
state = check_int_transfer(hcd, &ptd,
|
||||||
slots[slot].qtd->urb);
|
slots[slot].qtd->urb);
|
||||||
} else {
|
} else {
|
||||||
/* ATL ptd */
|
/* ATL ptd */
|
||||||
slot = __ffs(atl_done_map);
|
slot = __ffs(priv->atl_done_map);
|
||||||
atl_done_map &= ~(1 << slot);
|
priv->atl_done_map &= ~(1 << slot);
|
||||||
slots = priv->atl_slots;
|
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;
|
continue;
|
||||||
|
}
|
||||||
ptd_offset = ATL_PTD_OFFSET;
|
ptd_offset = ATL_PTD_OFFSET;
|
||||||
ptd_read(hcd->regs, ATL_PTD_OFFSET, slot, &ptd);
|
ptd_read(hcd->regs, ATL_PTD_OFFSET, slot, &ptd);
|
||||||
state = check_atl_transfer(hcd, &ptd,
|
state = check_atl_transfer(hcd, &ptd,
|
||||||
|
@ -1509,14 +1545,41 @@ static int isp1760_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
|
||||||
return retval;
|
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,
|
static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb,
|
||||||
int status)
|
int status)
|
||||||
{
|
{
|
||||||
struct isp1760_hcd *priv = hcd_to_priv(hcd);
|
struct isp1760_hcd *priv = hcd_to_priv(hcd);
|
||||||
|
unsigned long spinflags;
|
||||||
struct isp1760_qh *qh;
|
struct isp1760_qh *qh;
|
||||||
struct isp1760_qtd *qtd;
|
struct isp1760_qtd *qtd;
|
||||||
struct ptd ptd;
|
|
||||||
unsigned long spinflags;
|
|
||||||
int retval = 0;
|
int retval = 0;
|
||||||
|
|
||||||
spin_lock_irqsave(&priv->lock, spinflags);
|
spin_lock_irqsave(&priv->lock, spinflags);
|
||||||
|
@ -1527,34 +1590,18 @@ static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb,
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We need to forcefully reclaim the slot since some transfers never
|
list_for_each_entry(qtd, &qh->qtd_list, qtd_list)
|
||||||
return, e.g. interrupt transfers and NAKed bulk transfers. */
|
if (qtd->urb == urb) {
|
||||||
if (qh->slot > -1) {
|
if (qtd->status == QTD_XFER_STARTED)
|
||||||
memset(&ptd, 0, sizeof(ptd));
|
kill_transfer(hcd, urb, qh);
|
||||||
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)
|
|
||||||
qtd->status = QTD_RETIRE;
|
qtd->status = QTD_RETIRE;
|
||||||
}
|
}
|
||||||
|
|
||||||
urb->status = status;
|
urb->status = status;
|
||||||
schedule_ptds(hcd);
|
schedule_ptds(hcd);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
spin_unlock_irqrestore(&priv->lock, spinflags);
|
spin_unlock_irqrestore(&priv->lock, spinflags);
|
||||||
|
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1562,32 +1609,28 @@ static void isp1760_endpoint_disable(struct usb_hcd *hcd,
|
||||||
struct usb_host_endpoint *ep)
|
struct usb_host_endpoint *ep)
|
||||||
{
|
{
|
||||||
struct isp1760_hcd *priv = hcd_to_priv(hcd);
|
struct isp1760_hcd *priv = hcd_to_priv(hcd);
|
||||||
|
unsigned long spinflags;
|
||||||
struct isp1760_qh *qh;
|
struct isp1760_qh *qh;
|
||||||
struct isp1760_qtd *qtd;
|
struct isp1760_qtd *qtd;
|
||||||
unsigned long spinflags;
|
|
||||||
int do_iter;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&priv->lock, spinflags);
|
spin_lock_irqsave(&priv->lock, spinflags);
|
||||||
|
|
||||||
qh = ep->hcpriv;
|
qh = ep->hcpriv;
|
||||||
if (!qh)
|
if (!qh)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
do_iter = !list_empty(&qh->qtd_list);
|
list_for_each_entry(qtd, &qh->qtd_list, qtd_list) {
|
||||||
while (do_iter) {
|
if (qtd->status == QTD_XFER_STARTED)
|
||||||
do_iter = 0;
|
kill_transfer(hcd, qtd->urb, qh);
|
||||||
list_for_each_entry(qtd, &qh->qtd_list, qtd_list) {
|
qtd->status = QTD_RETIRE;
|
||||||
if (qtd->urb->ep == ep) {
|
qtd->urb->status = -ECONNRESET;
|
||||||
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 */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ep->hcpriv = NULL;
|
ep->hcpriv = NULL;
|
||||||
/* Cannot free qh here since it will be parsed by schedule_ptds() */
|
/* Cannot free qh here since it will be parsed by schedule_ptds() */
|
||||||
|
|
||||||
|
schedule_ptds(hcd);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
spin_unlock_irqrestore(&priv->lock, spinflags);
|
spin_unlock_irqrestore(&priv->lock, spinflags);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue