提交 d05b6ec0 编写于 作者: A Arvid Brodin 提交者: Greg Kroah-Hartman

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: NArvid Brodin <arvid.brodin@enea.com>
Signed-off-by: NGreg Kroah-Hartman <gregkh@suse.de>
上级 079cdb09
...@@ -34,7 +34,9 @@ struct isp1760_hcd { ...@@ -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) ...@@ -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, ...@@ -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, ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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,43 +1545,55 @@ static int isp1760_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, ...@@ -1509,43 +1545,55 @@ static int isp1760_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
return retval; return retval;
} }
static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, static void kill_transfer(struct usb_hcd *hcd, struct urb *urb,
int status) struct isp1760_qh *qh)
{ {
struct isp1760_hcd *priv = hcd_to_priv(hcd); struct isp1760_hcd *priv = hcd_to_priv(hcd);
struct isp1760_qh *qh; int skip_map;
struct isp1760_qtd *qtd;
struct ptd ptd;
unsigned long spinflags;
int retval = 0;
spin_lock_irqsave(&priv->lock, spinflags); WARN_ON(qh->slot == -1);
qh = urb->ep->hcpriv;
if (!qh) {
retval = -EINVAL;
goto out;
}
/* We need to forcefully reclaim the slot since some transfers never /* We need to forcefully reclaim the slot since some transfers never
return, e.g. interrupt transfers and NAKed bulk transfers. */ return, e.g. interrupt transfers and NAKed bulk transfers. */
if (qh->slot > -1) {
memset(&ptd, 0, sizeof(ptd));
if (usb_pipebulk(urb->pipe)) { 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].qh = NULL;
priv->atl_slots[qh->slot].qtd = NULL; priv->atl_slots[qh->slot].qtd = NULL;
ptd_write(hcd->regs, ATL_PTD_OFFSET, qh->slot, &ptd);
} else { } 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].qh = NULL;
priv->int_slots[qh->slot].qtd = NULL; priv->int_slots[qh->slot].qtd = NULL;
ptd_write(hcd->regs, INT_PTD_OFFSET, qh->slot, &ptd);
} }
priv->active_ptds--;
qh->slot = -1; 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;
int retval = 0;
spin_lock_irqsave(&priv->lock, spinflags);
qh = urb->ep->hcpriv;
if (!qh) {
retval = -EINVAL;
goto out;
} }
list_for_each_entry(qtd, &qh->qtd_list, qtd_list) { list_for_each_entry(qtd, &qh->qtd_list, qtd_list)
if (qtd->urb == urb) if (qtd->urb == urb) {
if (qtd->status == QTD_XFER_STARTED)
kill_transfer(hcd, urb, qh);
qtd->status = QTD_RETIRE; qtd->status = QTD_RETIRE;
} }
...@@ -1554,7 +1602,6 @@ static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, ...@@ -1554,7 +1602,6 @@ static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb,
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, ...@@ -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);
while (do_iter) {
do_iter = 0;
list_for_each_entry(qtd, &qh->qtd_list, qtd_list) { list_for_each_entry(qtd, &qh->qtd_list, qtd_list) {
if (qtd->urb->ep == ep) { if (qtd->status == QTD_XFER_STARTED)
spin_unlock_irqrestore(&priv->lock, spinflags); kill_transfer(hcd, qtd->urb, qh);
isp1760_urb_dequeue(hcd, qtd->urb, -ECONNRESET); qtd->status = QTD_RETIRE;
spin_lock_irqsave(&priv->lock, spinflags); qtd->urb->status = -ECONNRESET;
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);
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册