* Maintainer: Johannes Erdfelt <johannes@erdfelt.com>
*
* (C) Copyright 1999 Linus Torvalds
- * (C) Copyright 1999-2001 Johannes Erdfelt, johannes@erdfelt.com
+ * (C) Copyright 1999-2002 Johannes Erdfelt, johannes@erdfelt.com
* (C) Copyright 1999 Randy Dunlap
* (C) Copyright 1999 Georg Acher, acher@in.tum.de
* (C) Copyright 1999 Deti Fliegl, deti@fliegl.de
unsigned long flags;
/* If it's not inserted, don't remove it */
+ spin_lock_irqsave(&uhci->frame_list_lock, flags);
if (td->frame == -1 && list_empty(&td->fl_list))
- return;
+ goto out;
- spin_lock_irqsave(&uhci->frame_list_lock, flags);
if (td->frame != -1 && uhci->fl->frame_cpu[td->frame] == td) {
if (list_empty(&td->fl_list)) {
uhci->fl->frame[td->frame] = td->link;
list_del_init(&td->fl_list);
td->frame = -1;
+out:
spin_unlock_irqrestore(&uhci->frame_list_lock, flags);
}
pci_pool_free(uhci->qh_pool, qh, qh->dma_handle);
}
+/*
+ * MUST be called with uhci->frame_list_lock acquired
+ */
static void _uhci_insert_qh(struct uhci *uhci, struct uhci_qh *skelqh, struct urb *urb)
{
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
return;
/* Only go through the hoops if it's actually linked in */
+ spin_lock_irqsave(&uhci->frame_list_lock, flags);
if (!list_empty(&qh->list)) {
qh->urbp = NULL;
- spin_lock_irqsave(&uhci->frame_list_lock, flags);
-
pqh = list_entry(qh->list.prev, struct uhci_qh, list);
if (pqh->urbp) {
qh->element = qh->link = UHCI_PTR_TERM;
list_del_init(&qh->list);
-
- spin_unlock_irqrestore(&uhci->frame_list_lock, flags);
}
+ spin_unlock_irqrestore(&uhci->frame_list_lock, flags);
spin_lock_irqsave(&uhci->qh_remove_list_lock, flags);
return urbp;
}
+/*
+ * MUST be called with urb->lock acquired
+ */
static void uhci_add_td_to_urb(struct urb *urb, struct uhci_td *td)
{
struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
list_add_tail(&td->list, &urbp->td_list);
}
+/*
+ * MUST be called with urb->lock acquired
+ */
static void uhci_remove_td_from_urb(struct uhci_td *td)
{
if (list_empty(&td->list))
td->urb = NULL;
}
+/*
+ * MUST be called with urb->lock acquired
+ */
static void uhci_destroy_urb_priv(struct urb *urb)
{
struct list_head *head, *tmp;
struct urb_priv *urbp;
struct uhci *uhci;
- unsigned long flags;
-
- spin_lock_irqsave(&urb->lock, flags);
urbp = (struct urb_priv *)urb->hcpriv;
if (!urbp)
- goto out;
+ return;
if (!urbp->dev || !urbp->dev->bus || !urbp->dev->bus->hcpriv) {
warn("uhci_destroy_urb_priv: urb %p belongs to disconnected device or bus?", urb);
- goto out;
+ return;
}
if (!list_empty(&urb->urb_list))
urb->hcpriv = NULL;
kmem_cache_free(uhci_up_cachep, urbp);
-
-out:
- spin_unlock_irqrestore(&urb->lock, flags);
}
static void uhci_inc_fsbr(struct uhci *uhci, struct urb *urb)
* It's IN if the pipe is an output pipe or we're not expecting
* data back.
*/
- destination &= ~TD_PID;
+ destination &= ~TD_TOKEN_PID_MASK;
if (usb_pipeout(urb->pipe) || !urb->transfer_buffer_length)
destination |= USB_PID_IN;
else
struct uhci *uhci = (struct uhci *)urb->dev->bus->hcpriv;
struct list_head *tmp, *head;
int ret = 0;
- unsigned long flags;
- spin_lock_irqsave(&uhci->urb_list_lock, flags);
head = &uhci->urb_list;
tmp = head->next;
while (tmp != head) {
} else
ret = -1; /* no previous urb found */
- spin_unlock_irqrestore(&uhci->urb_list_lock, flags);
-
return ret;
}
return ret;
}
+/*
+ * MUST be called with uhci->urb_list_lock acquired
+ */
static struct urb *uhci_find_urb_ep(struct uhci *uhci, struct urb *urb)
{
struct list_head *tmp, *head;
- unsigned long flags;
- struct urb *u = NULL;
/* We don't match Isoc transfers since they are special */
if (usb_pipeisoc(urb->pipe))
return NULL;
- spin_lock_irqsave(&uhci->urb_list_lock, flags);
head = &uhci->urb_list;
tmp = head->next;
while (tmp != head) {
- u = list_entry(tmp, struct urb, urb_list);
+ struct urb *u = list_entry(tmp, struct urb, urb_list);
tmp = tmp->next;
if (u->dev == urb->dev && u->pipe == urb->pipe &&
u->status == -EINPROGRESS)
- goto out;
+ return u;
}
- u = NULL;
-
-out:
- spin_unlock_irqrestore(&uhci->urb_list_lock, flags);
- return u;
+ return NULL;
}
static int uhci_submit_urb(struct urb *urb, int mem_flags)
INIT_LIST_HEAD(&urb->urb_list);
usb_inc_dev_use(urb->dev);
- spin_lock_irqsave(&urb->lock, flags);
+ spin_lock_irqsave(&uhci->urb_list_lock, flags);
+ spin_lock(&urb->lock);
if (urb->status == -EINPROGRESS || urb->status == -ECONNRESET ||
urb->status == -ECONNABORTED) {
dbg("uhci_submit_urb: urb not available to submit (status = %d)", urb->status);
/* Since we can have problems on the out path */
- spin_unlock_irqrestore(&urb->lock, flags);
+ spin_unlock(&urb->lock);
+ spin_unlock_irqrestore(&uhci->urb_list_lock, flags);
usb_dec_dev_use(urb->dev);
usb_put_urb(urb);
out:
urb->status = ret;
- spin_unlock_irqrestore(&urb->lock, flags);
-
if (ret == -EINPROGRESS) {
- spin_lock_irqsave(&uhci->urb_list_lock, flags);
/* We use _tail to make find_urb_ep more efficient */
list_add_tail(&urb->urb_list, &uhci->urb_list);
+
+ spin_unlock(&urb->lock);
spin_unlock_irqrestore(&uhci->urb_list_lock, flags);
return 0;
}
uhci_unlink_generic(uhci, urb);
+
+ spin_unlock(&urb->lock);
+ spin_unlock_irqrestore(&uhci->urb_list_lock, flags);
+
uhci_call_completion(urb);
return ret;
/*
* Return the result of a transfer
*
- * Must be called with urb_list_lock acquired
+ * MUST be called with urb_list_lock acquired
*/
static void uhci_transfer_result(struct uhci *uhci, struct urb *urb)
{
urbp->status = ret;
- spin_unlock_irqrestore(&urb->lock, flags);
-
- if (ret == -EINPROGRESS)
+ if (ret == -EINPROGRESS) {
+ spin_unlock_irqrestore(&urb->lock, flags);
return;
+ }
switch (usb_pipetype(urb->pipe)) {
case PIPE_CONTROL:
usb_pipetype(urb->pipe), urb);
}
+ /* Remove it from uhci->urb_list */
list_del_init(&urb->urb_list);
uhci_add_complete(urb);
+
+ spin_unlock_irqrestore(&urb->lock, flags);
}
+/*
+ * MUST be called with urb->lock acquired
+ */
static void uhci_unlink_generic(struct uhci *uhci, struct urb *urb)
{
struct list_head *head, *tmp;
uhci = (struct uhci *)urb->dev->bus->hcpriv;
+ spin_lock_irqsave(&uhci->urb_list_lock, flags);
+ spin_lock(&urb->lock);
+
/* Release bandwidth for Interrupt or Isoc. transfers */
/* Spinlock needed ? */
if (urb->bandwidth) {
}
}
- if (urb->status != -EINPROGRESS)
+ if (urb->status != -EINPROGRESS) {
+ spin_unlock(&urb->lock);
+ spin_unlock_irqrestore(&uhci->urb_list_lock, flags);
return 0;
+ }
- spin_lock_irqsave(&uhci->urb_list_lock, flags);
list_del_init(&urb->urb_list);
- spin_unlock_irqrestore(&uhci->urb_list_lock, flags);
uhci_unlink_generic(uhci, urb);
/* Short circuit the virtual root hub */
if (urb->dev == uhci->rh.dev) {
rh_unlink_urb(urb);
+
+ spin_unlock(&urb->lock);
+ spin_unlock_irqrestore(&uhci->urb_list_lock, flags);
+
uhci_call_completion(urb);
} else {
if (urb->transfer_flags & USB_ASYNC_UNLINK) {
- /* urb_list is available now since we called */
- /* uhci_unlink_generic already */
-
urbp->status = urb->status = -ECONNABORTED;
- spin_lock_irqsave(&uhci->urb_remove_list_lock, flags);
+ spin_lock(&uhci->urb_remove_list_lock);
- /* Check to see if the remove list is empty */
+ /* If we're the first, set the next interrupt bit */
if (list_empty(&uhci->urb_remove_list))
uhci_set_next_interrupt(uhci);
list_add(&urb->urb_list, &uhci->urb_remove_list);
- spin_unlock_irqrestore(&uhci->urb_remove_list_lock, flags);
+ spin_unlock(&uhci->urb_remove_list_lock);
+
+ spin_unlock(&urb->lock);
+ spin_unlock_irqrestore(&uhci->urb_list_lock, flags);
+
} else {
urb->status = -ENOENT;
} else
schedule_timeout(1+1*HZ/1000);
+ spin_unlock(&urb->lock);
+ spin_unlock_irqrestore(&uhci->urb_list_lock, flags);
+
uhci_call_completion(urb);
}
}
/* prepare Interrupt pipe transaction data; HUB INTERRUPT ENDPOINT */
static int rh_send_irq(struct urb *urb)
{
- int i, len = 1;
struct uhci *uhci = (struct uhci *)urb->dev->bus->hcpriv;
+ struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
unsigned int io_addr = uhci->io_addr;
+ unsigned long flags;
+ int i, len = 1;
__u16 data = 0;
- struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
+ spin_lock_irqsave(&urb->lock, flags);
for (i = 0; i < uhci->rh.numports; i++) {
data |= ((inw(io_addr + USBPORTSC1 + i * 2) & 0xa) > 0 ? (1 << (i + 1)) : 0);
len = (i + 1) / 8 + 1;
urb->actual_length = len;
urbp->status = 0;
+ spin_unlock_irqrestore(&urb->lock, flags);
+
if ((data > 0) && (uhci->rh.send != 0)) {
dbg("root-hub INT complete: port1: %x port2: %x data: %x",
inw(io_addr + USBPORTSC1), inw(io_addr + USBPORTSC2), data);
spin_lock_irqsave(&uhci->urb_list_lock, flags);
head = &uhci->urb_list;
-
tmp = head->next;
while (tmp != head) {
struct urb *u = list_entry(tmp, struct urb, urb_list);
tmp = tmp->next;
+ spin_lock(&urb->lock);
+
/* Check if the FSBR timed out */
if (urbp->fsbr && !urbp->fsbr_timeout && time_after_eq(jiffies, urbp->fsbrtime + IDLE_TIMEOUT))
uhci_fsbr_timeout(uhci, u);
list_del(&u->urb_list);
list_add_tail(&u->urb_list, &list);
}
+
+ spin_unlock(&urb->lock);
}
spin_unlock_irqrestore(&uhci->urb_list_lock, flags);
return stat;
}
+/*
+ * MUST be called with urb->lock acquired
+ */
static int rh_unlink_urb(struct urb *urb)
{
struct uhci *uhci = (struct uhci *)urb->dev->bus->hcpriv;
static void uhci_call_completion(struct urb *urb)
{
- struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
+ struct urb_priv *urbp;
struct usb_device *dev = urb->dev;
struct uhci *uhci = (struct uhci *)dev->bus->hcpriv;
int is_ring = 0, killed, resubmit_interrupt, status;
struct urb *nurb;
+ unsigned long flags;
+
+ spin_lock_irqsave(&urb->lock, flags);
+
+ urbp = (struct urb_priv *)urb->hcpriv;
+ if (!urbp || !urb->dev) {
+ spin_unlock_irqrestore(&urb->lock, flags);
+ return;
+ }
killed = (urb->status == -ENOENT || urb->status == -ECONNABORTED ||
urb->status == -ECONNRESET);
urb->status = status;
urb->dev = NULL;
+ spin_unlock_irqrestore(&urb->lock, flags);
+
if (urb->complete) {
urb->complete(urb);
struct urb_priv *urbp = list_entry(tmp, struct urb_priv, complete_list);
struct urb *urb = urbp->urb;
- tmp = tmp->next;
-
list_del_init(&urbp->complete_list);
+ spin_unlock_irqrestore(&uhci->complete_list_lock, flags);
uhci_call_completion(urb);
+
+ spin_lock_irqsave(&uhci->complete_list_lock, flags);
+ head = &uhci->complete_list;
+ tmp = head->next;
}
spin_unlock_irqrestore(&uhci->complete_list_lock, flags);
}
list_del_init(&urb->urb_list);
urbp->status = urb->status = -ECONNRESET;
- uhci_call_completion(urb);
+
+ uhci_add_complete(urb);
}
spin_unlock_irqrestore(&uhci->urb_remove_list_lock, flags);
}
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
+
* for TD <info>: (a.k.a. Token)
*/
#define TD_TOKEN_TOGGLE 19
-#define TD_PID 0xFF
+#define TD_TOKEN_PID_MASK 0xFF
+#define TD_TOKEN_EXPLEN_MASK 0x7FF /* expected length, encoded as n - 1 */
#define uhci_maxlen(token) ((token) >> 21)
-#define uhci_expected_length(info) (((info >> 21) + 1) & TD_CTRL_ACTLEN_MASK) /* 1-based */
+#define uhci_expected_length(info) (((info >> 21) + 1) & TD_TOKEN_EXPLEN_MASK) /* 1-based */
#define uhci_toggle(token) (((token) >> TD_TOKEN_TOGGLE) & 1)
#define uhci_endpoint(token) (((token) >> 15) & 0xf)
#define uhci_devaddr(token) (((token) >> 8) & 0x7f)
#define uhci_devep(token) (((token) >> 8) & 0x7ff)
-#define uhci_packetid(token) ((token) & 0xff)
+#define uhci_packetid(token) ((token) & TD_TOKEN_PID_MASK)
#define uhci_packetout(token) (uhci_packetid(token) != USB_PID_IN)
#define uhci_packetin(token) (uhci_packetid(token) == USB_PID_IN)
struct list_head list; /* P: urb->lock */
int frame;
- struct list_head fl_list; /* P: frame_list_lock */
+ struct list_head fl_list; /* P: uhci->frame_list_lock */
} __attribute__((aligned(16)));
/*
struct uhci_qh *skelqh[UHCI_NUM_SKELQH]; /* Skeleton QH's */
spinlock_t frame_list_lock;
- struct uhci_frame_list *fl; /* Frame list */
+ struct uhci_frame_list *fl; /* P: uhci->frame_list_lock */
int fsbr; /* Full speed bandwidth reclamation */
int is_suspended;
+ /* Main list of URB's currently controlled by this HC */
+ spinlock_t urb_list_lock;
+ struct list_head urb_list; /* P: uhci->urb_list_lock */
+
+ /* List of QH's that are done, but waiting to be unlinked (race) */
spinlock_t qh_remove_list_lock;
- struct list_head qh_remove_list;
+ struct list_head qh_remove_list; /* P: uhci->qh_remove_list_lock */
+ /* List of asynchronously unlinked URB's */
spinlock_t urb_remove_list_lock;
- struct list_head urb_remove_list;
-
- spinlock_t urb_list_lock;
- struct list_head urb_list;
+ struct list_head urb_remove_list; /* P: uhci->urb_remove_list_lock */
+ /* List of URB's awaiting completion callback */
spinlock_t complete_list_lock;
- struct list_head complete_list;
+ struct list_head complete_list; /* P: uhci->complete_list_lock */
struct virt_root_hub rh; /* private data of the virtual root hub */
};
dma_addr_t transfer_buffer_dma_handle;
struct uhci_qh *qh; /* QH for this URB */
- struct list_head td_list;
+ struct list_head td_list; /* P: urb->lock */
int fsbr : 1; /* URB turned on FSBR */
int fsbr_timeout : 1; /* URB timed out on FSBR */
int status; /* Final status */
unsigned long inserttime; /* In jiffies */
- unsigned long fsbrtime; /* In jiffies */
+ unsigned long fsbrtime; /* In jiffies */
- struct list_head queue_list;
- struct list_head complete_list;
+ struct list_head queue_list; /* P: uhci->frame_list_lock */
+ struct list_head complete_list; /* P: uhci->complete_list_lock */
};
+/*
+ * Locking in uhci.c
+ *
+ * spinlocks are used extensively to protect the many lists and data
+ * structures we have. It's not that pretty, but it's necessary. We
+ * need to be done with all of the locks (except complete_list_lock) when
+ * we call urb->complete. I've tried to make it simple enough so I don't
+ * have to spend hours racking my brain trying to figure out if the
+ * locking is safe.
+ *
+ * Here's the safe locking order to prevent deadlocks:
+ *
+ * #1 uhci->urb_list_lock
+ * #2 urb->lock
+ * #3 uhci->urb_remove_list_lock, uhci->frame_list_lock,
+ * uhci->qh_remove_list_lock
+ * #4 uhci->complete_list_lock
+ *
+ * If you're going to grab 2 or more locks at once, ALWAYS grab the lock
+ * at the lowest level FIRST and NEVER grab locks at the same level at the
+ * same time.
+ *
+ * So, if you need uhci->urb_list_lock, grab it before you grab urb->lock
+ */
+
/* -------------------------------------------------------------------------
Virtual Root HUB
------------------------------------------------------------------------- */