diff options
author | Arvid Brodin <arvid.brodin@enea.com> | 2011-05-19 18:17:45 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2011-05-19 19:34:04 -0400 |
commit | d05b6ec01b8186f847ac9e41098e40858926db40 (patch) | |
tree | 12ac448547d00704cd15b293732c17d036ddae10 /drivers/usb/host | |
parent | 079cdb0947ce6ae7df0c73a1c82c14920a9b6b6d (diff) |
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>
Diffstat (limited to 'drivers/usb/host')
-rw-r--r-- | drivers/usb/host/isp1760-hcd.c | 147 |
1 files changed, 95 insertions, 52 deletions
diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 485fc70625a1..c9e6e454c625 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c | |||
@@ -34,7 +34,9 @@ struct isp1760_hcd { | |||
34 | u32 hcs_params; | 34 | u32 hcs_params; |
35 | spinlock_t lock; | 35 | spinlock_t lock; |
36 | struct slotinfo atl_slots[32]; | 36 | struct slotinfo atl_slots[32]; |
37 | int atl_done_map; | ||
37 | struct slotinfo int_slots[32]; | 38 | struct slotinfo int_slots[32]; |
39 | int int_done_map; | ||
38 | struct memory_chunk memory_pool[BLOCKS]; | 40 | struct memory_chunk memory_pool[BLOCKS]; |
39 | struct list_head controlqhs, bulkqhs, interruptqhs; | 41 | struct list_head controlqhs, bulkqhs, interruptqhs; |
40 | int active_ptds; | 42 | int active_ptds; |
@@ -519,9 +521,9 @@ static void isp1760_init_maps(struct usb_hcd *hcd) | |||
519 | reg_write32(hcd->regs, HC_INT_PTD_LASTPTD_REG, 0x80000000); | 521 | reg_write32(hcd->regs, HC_INT_PTD_LASTPTD_REG, 0x80000000); |
520 | reg_write32(hcd->regs, HC_ISO_PTD_LASTPTD_REG, 0x00000001); | 522 | reg_write32(hcd->regs, HC_ISO_PTD_LASTPTD_REG, 0x00000001); |
521 | 523 | ||
522 | reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, 0); | 524 | reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, 0xffffffff); |
523 | reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, 0); | 525 | reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, 0xffffffff); |
524 | reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, 0); | 526 | reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, 0xffffffff); |
525 | 527 | ||
526 | reg_write32(hcd->regs, HC_BUFFER_STATUS_REG, | 528 | reg_write32(hcd->regs, HC_BUFFER_STATUS_REG, |
527 | ATL_BUF_FILL | INT_BUF_FILL); | 529 | 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 | struct isp1760_qh *qh, struct ptd *ptd) | 805 | struct isp1760_qh *qh, struct ptd *ptd) |
804 | { | 806 | { |
805 | struct isp1760_hcd *priv = hcd_to_priv(hcd); | 807 | struct isp1760_hcd *priv = hcd_to_priv(hcd); |
808 | int skip_map; | ||
809 | |||
806 | WARN_ON((slot < 0) || (slot > 31)); | 810 | WARN_ON((slot < 0) || (slot > 31)); |
807 | WARN_ON(qtd->length && !qtd->payload_addr); | 811 | WARN_ON(qtd->length && !qtd->payload_addr); |
808 | WARN_ON(slots[slot].qtd); | 812 | WARN_ON(slots[slot].qtd); |
@@ -816,6 +820,25 @@ static void start_bus_transfer(struct usb_hcd *hcd, u32 ptd_offset, int slot, | |||
816 | interrupt routine may preempt and expects this value. */ | 820 | interrupt routine may preempt and expects this value. */ |
817 | ptd_write(hcd->regs, ptd_offset, slot, ptd); | 821 | ptd_write(hcd->regs, ptd_offset, slot, ptd); |
818 | priv->active_ptds++; | 822 | priv->active_ptds++; |
823 | |||
824 | /* Make sure done map has not triggered from some unlinked transfer */ | ||
825 | if (ptd_offset == ATL_PTD_OFFSET) { | ||
826 | priv->atl_done_map |= reg_read32(hcd->regs, | ||
827 | HC_ATL_PTD_DONEMAP_REG); | ||
828 | priv->atl_done_map &= ~(1 << qh->slot); | ||
829 | |||
830 | skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); | ||
831 | skip_map &= ~(1 << qh->slot); | ||
832 | reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, skip_map); | ||
833 | } else { | ||
834 | priv->int_done_map |= reg_read32(hcd->regs, | ||
835 | HC_INT_PTD_DONEMAP_REG); | ||
836 | priv->int_done_map &= ~(1 << qh->slot); | ||
837 | |||
838 | skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); | ||
839 | skip_map &= ~(1 << qh->slot); | ||
840 | reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, skip_map); | ||
841 | } | ||
819 | } | 842 | } |
820 | 843 | ||
821 | static int is_short_bulk(struct isp1760_qtd *qtd) | 844 | static int is_short_bulk(struct isp1760_qtd *qtd) |
@@ -1152,7 +1175,6 @@ static irqreturn_t isp1760_irq(struct usb_hcd *hcd) | |||
1152 | irqreturn_t irqret = IRQ_NONE; | 1175 | irqreturn_t irqret = IRQ_NONE; |
1153 | struct ptd ptd; | 1176 | struct ptd ptd; |
1154 | struct isp1760_qh *qh; | 1177 | struct isp1760_qh *qh; |
1155 | int int_done_map, atl_done_map; | ||
1156 | int slot; | 1178 | int slot; |
1157 | int state; | 1179 | int state; |
1158 | struct slotinfo *slots; | 1180 | struct slotinfo *slots; |
@@ -1160,6 +1182,7 @@ static irqreturn_t isp1760_irq(struct usb_hcd *hcd) | |||
1160 | struct isp1760_qtd *qtd; | 1182 | struct isp1760_qtd *qtd; |
1161 | int modified; | 1183 | int modified; |
1162 | static int last_active_ptds; | 1184 | static int last_active_ptds; |
1185 | int int_skip_map, atl_skip_map; | ||
1163 | 1186 | ||
1164 | spin_lock(&priv->lock); | 1187 | spin_lock(&priv->lock); |
1165 | 1188 | ||
@@ -1171,29 +1194,42 @@ static irqreturn_t isp1760_irq(struct usb_hcd *hcd) | |||
1171 | goto leave; | 1194 | goto leave; |
1172 | reg_write32(hcd->regs, HC_INTERRUPT_REG, imask); /* Clear */ | 1195 | reg_write32(hcd->regs, HC_INTERRUPT_REG, imask); /* Clear */ |
1173 | 1196 | ||
1174 | int_done_map = reg_read32(hcd->regs, HC_INT_PTD_DONEMAP_REG); | 1197 | int_skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); |
1175 | atl_done_map = reg_read32(hcd->regs, HC_ATL_PTD_DONEMAP_REG); | 1198 | atl_skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); |
1176 | modified = int_done_map | atl_done_map; | 1199 | priv->int_done_map |= reg_read32(hcd->regs, HC_INT_PTD_DONEMAP_REG); |
1200 | priv->atl_done_map |= reg_read32(hcd->regs, HC_ATL_PTD_DONEMAP_REG); | ||
1201 | priv->int_done_map &= ~int_skip_map; | ||
1202 | priv->atl_done_map &= ~atl_skip_map; | ||
1177 | 1203 | ||
1178 | while (int_done_map || atl_done_map) { | 1204 | modified = priv->int_done_map | priv->atl_done_map; |
1179 | if (int_done_map) { | 1205 | |
1206 | while (priv->int_done_map || priv->atl_done_map) { | ||
1207 | if (priv->int_done_map) { | ||
1180 | /* INT ptd */ | 1208 | /* INT ptd */ |
1181 | slot = __ffs(int_done_map); | 1209 | slot = __ffs(priv->int_done_map); |
1182 | int_done_map &= ~(1 << slot); | 1210 | priv->int_done_map &= ~(1 << slot); |
1183 | slots = priv->int_slots; | 1211 | slots = priv->int_slots; |
1184 | if (!slots[slot].qh) | 1212 | /* This should not trigger, and could be removed if |
1213 | noone have any problems with it triggering: */ | ||
1214 | if (!slots[slot].qh) { | ||
1215 | WARN_ON(1); | ||
1185 | continue; | 1216 | continue; |
1217 | } | ||
1186 | ptd_offset = INT_PTD_OFFSET; | 1218 | ptd_offset = INT_PTD_OFFSET; |
1187 | ptd_read(hcd->regs, INT_PTD_OFFSET, slot, &ptd); | 1219 | ptd_read(hcd->regs, INT_PTD_OFFSET, slot, &ptd); |
1188 | state = check_int_transfer(hcd, &ptd, | 1220 | state = check_int_transfer(hcd, &ptd, |
1189 | slots[slot].qtd->urb); | 1221 | slots[slot].qtd->urb); |
1190 | } else { | 1222 | } else { |
1191 | /* ATL ptd */ | 1223 | /* ATL ptd */ |
1192 | slot = __ffs(atl_done_map); | 1224 | slot = __ffs(priv->atl_done_map); |
1193 | atl_done_map &= ~(1 << slot); | 1225 | priv->atl_done_map &= ~(1 << slot); |
1194 | slots = priv->atl_slots; | 1226 | slots = priv->atl_slots; |
1195 | if (!slots[slot].qh) | 1227 | /* This should not trigger, and could be removed if |
1228 | noone have any problems with it triggering: */ | ||
1229 | if (!slots[slot].qh) { | ||
1230 | WARN_ON(1); | ||
1196 | continue; | 1231 | continue; |
1232 | } | ||
1197 | ptd_offset = ATL_PTD_OFFSET; | 1233 | ptd_offset = ATL_PTD_OFFSET; |
1198 | ptd_read(hcd->regs, ATL_PTD_OFFSET, slot, &ptd); | 1234 | ptd_read(hcd->regs, ATL_PTD_OFFSET, slot, &ptd); |
1199 | state = check_atl_transfer(hcd, &ptd, | 1235 | state = check_atl_transfer(hcd, &ptd, |
@@ -1509,14 +1545,41 @@ out: | |||
1509 | return retval; | 1545 | return retval; |
1510 | } | 1546 | } |
1511 | 1547 | ||
1548 | static void kill_transfer(struct usb_hcd *hcd, struct urb *urb, | ||
1549 | struct isp1760_qh *qh) | ||
1550 | { | ||
1551 | struct isp1760_hcd *priv = hcd_to_priv(hcd); | ||
1552 | int skip_map; | ||
1553 | |||
1554 | WARN_ON(qh->slot == -1); | ||
1555 | |||
1556 | /* We need to forcefully reclaim the slot since some transfers never | ||
1557 | return, e.g. interrupt transfers and NAKed bulk transfers. */ | ||
1558 | if (usb_pipebulk(urb->pipe)) { | ||
1559 | skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); | ||
1560 | skip_map |= (1 << qh->slot); | ||
1561 | reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, skip_map); | ||
1562 | priv->atl_slots[qh->slot].qh = NULL; | ||
1563 | priv->atl_slots[qh->slot].qtd = NULL; | ||
1564 | } else { | ||
1565 | skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); | ||
1566 | skip_map |= (1 << qh->slot); | ||
1567 | reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, skip_map); | ||
1568 | priv->int_slots[qh->slot].qh = NULL; | ||
1569 | priv->int_slots[qh->slot].qtd = NULL; | ||
1570 | } | ||
1571 | |||
1572 | qh->slot = -1; | ||
1573 | priv->active_ptds--; | ||
1574 | } | ||
1575 | |||
1512 | static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, | 1576 | static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, |
1513 | int status) | 1577 | int status) |
1514 | { | 1578 | { |
1515 | struct isp1760_hcd *priv = hcd_to_priv(hcd); | 1579 | struct isp1760_hcd *priv = hcd_to_priv(hcd); |
1580 | unsigned long spinflags; | ||
1516 | struct isp1760_qh *qh; | 1581 | struct isp1760_qh *qh; |
1517 | struct isp1760_qtd *qtd; | 1582 | struct isp1760_qtd *qtd; |
1518 | struct ptd ptd; | ||
1519 | unsigned long spinflags; | ||
1520 | int retval = 0; | 1583 | int retval = 0; |
1521 | 1584 | ||
1522 | spin_lock_irqsave(&priv->lock, spinflags); | 1585 | spin_lock_irqsave(&priv->lock, spinflags); |
@@ -1527,34 +1590,18 @@ static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, | |||
1527 | goto out; | 1590 | goto out; |
1528 | } | 1591 | } |
1529 | 1592 | ||
1530 | /* We need to forcefully reclaim the slot since some transfers never | 1593 | list_for_each_entry(qtd, &qh->qtd_list, qtd_list) |
1531 | return, e.g. interrupt transfers and NAKed bulk transfers. */ | 1594 | if (qtd->urb == urb) { |
1532 | if (qh->slot > -1) { | 1595 | if (qtd->status == QTD_XFER_STARTED) |
1533 | memset(&ptd, 0, sizeof(ptd)); | 1596 | kill_transfer(hcd, urb, qh); |
1534 | if (usb_pipebulk(urb->pipe)) { | ||
1535 | priv->atl_slots[qh->slot].qh = NULL; | ||
1536 | priv->atl_slots[qh->slot].qtd = NULL; | ||
1537 | ptd_write(hcd->regs, ATL_PTD_OFFSET, qh->slot, &ptd); | ||
1538 | } else { | ||
1539 | priv->int_slots[qh->slot].qh = NULL; | ||
1540 | priv->int_slots[qh->slot].qtd = NULL; | ||
1541 | ptd_write(hcd->regs, INT_PTD_OFFSET, qh->slot, &ptd); | ||
1542 | } | ||
1543 | priv->active_ptds--; | ||
1544 | qh->slot = -1; | ||
1545 | } | ||
1546 | |||
1547 | list_for_each_entry(qtd, &qh->qtd_list, qtd_list) { | ||
1548 | if (qtd->urb == urb) | ||
1549 | qtd->status = QTD_RETIRE; | 1597 | qtd->status = QTD_RETIRE; |
1550 | } | 1598 | } |
1551 | 1599 | ||
1552 | urb->status = status; | 1600 | urb->status = status; |
1553 | schedule_ptds(hcd); | 1601 | schedule_ptds(hcd); |
1554 | 1602 | ||
1555 | out: | 1603 | out: |
1556 | spin_unlock_irqrestore(&priv->lock, spinflags); | 1604 | spin_unlock_irqrestore(&priv->lock, spinflags); |
1557 | |||
1558 | return retval; | 1605 | return retval; |
1559 | } | 1606 | } |
1560 | 1607 | ||
@@ -1562,32 +1609,28 @@ static void isp1760_endpoint_disable(struct usb_hcd *hcd, | |||
1562 | struct usb_host_endpoint *ep) | 1609 | struct usb_host_endpoint *ep) |
1563 | { | 1610 | { |
1564 | struct isp1760_hcd *priv = hcd_to_priv(hcd); | 1611 | struct isp1760_hcd *priv = hcd_to_priv(hcd); |
1612 | unsigned long spinflags; | ||
1565 | struct isp1760_qh *qh; | 1613 | struct isp1760_qh *qh; |
1566 | struct isp1760_qtd *qtd; | 1614 | struct isp1760_qtd *qtd; |
1567 | unsigned long spinflags; | ||
1568 | int do_iter; | ||
1569 | 1615 | ||
1570 | spin_lock_irqsave(&priv->lock, spinflags); | 1616 | spin_lock_irqsave(&priv->lock, spinflags); |
1617 | |||
1571 | qh = ep->hcpriv; | 1618 | qh = ep->hcpriv; |
1572 | if (!qh) | 1619 | if (!qh) |
1573 | goto out; | 1620 | goto out; |
1574 | 1621 | ||
1575 | do_iter = !list_empty(&qh->qtd_list); | 1622 | list_for_each_entry(qtd, &qh->qtd_list, qtd_list) { |
1576 | while (do_iter) { | 1623 | if (qtd->status == QTD_XFER_STARTED) |
1577 | do_iter = 0; | 1624 | kill_transfer(hcd, qtd->urb, qh); |
1578 | list_for_each_entry(qtd, &qh->qtd_list, qtd_list) { | 1625 | qtd->status = QTD_RETIRE; |
1579 | if (qtd->urb->ep == ep) { | 1626 | qtd->urb->status = -ECONNRESET; |
1580 | spin_unlock_irqrestore(&priv->lock, spinflags); | ||
1581 | isp1760_urb_dequeue(hcd, qtd->urb, -ECONNRESET); | ||
1582 | spin_lock_irqsave(&priv->lock, spinflags); | ||
1583 | do_iter = 1; | ||
1584 | break; /* Restart iteration */ | ||
1585 | } | ||
1586 | } | ||
1587 | } | 1627 | } |
1628 | |||
1588 | ep->hcpriv = NULL; | 1629 | ep->hcpriv = NULL; |
1589 | /* Cannot free qh here since it will be parsed by schedule_ptds() */ | 1630 | /* Cannot free qh here since it will be parsed by schedule_ptds() */ |
1590 | 1631 | ||
1632 | schedule_ptds(hcd); | ||
1633 | |||
1591 | out: | 1634 | out: |
1592 | spin_unlock_irqrestore(&priv->lock, spinflags); | 1635 | spin_unlock_irqrestore(&priv->lock, spinflags); |
1593 | } | 1636 | } |