aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/drivers/usb/host/uhci-q.c
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2006-05-19 16:44:55 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2006-06-21 15:04:12 -0700
commitcaf3827a65af476c71eaeb79636869a4ab128d48 (patch)
tree78ef5d5ec466e59d9ac626faf34fb1031ba4fd8e /drivers/usb/host/uhci-q.c
parent[PATCH] UHCI: fix race in ISO dequeuing (diff)
downloadwireguard-linux-caf3827a65af476c71eaeb79636869a4ab128d48.tar.xz
wireguard-linux-caf3827a65af476c71eaeb79636869a4ab128d48.zip
[PATCH] UHCI: store the period in the queue header
This patch (as689) stores the period for periodic transfers (interrupt and ISO) in the queue header. This is necessary for proper bandwidth tracking (not yet implemented). It also makes the scheduling of ISO transfers a bit more rigorous, with checks for out-of-bounds frame numbers. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/host/uhci-q.c')
-rw-r--r--drivers/usb/host/uhci-q.c72
1 files changed, 55 insertions, 17 deletions
diff --git a/drivers/usb/host/uhci-q.c b/drivers/usb/host/uhci-q.c
index 96ce4c87c871..7acc23473c63 100644
--- a/drivers/usb/host/uhci-q.c
+++ b/drivers/usb/host/uhci-q.c
@@ -763,6 +763,7 @@ static int uhci_submit_common(struct uhci_hcd *uhci, struct urb *urb,
wmb();
qh->dummy_td->status |= __constant_cpu_to_le32(TD_CTRL_ACTIVE);
qh->dummy_td = td;
+ qh->period = urb->interval;
usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe),
usb_pipeout(urb->pipe), toggle);
@@ -790,14 +791,30 @@ static inline int uhci_submit_bulk(struct uhci_hcd *uhci, struct urb *urb,
return ret;
}
-static inline int uhci_submit_interrupt(struct uhci_hcd *uhci, struct urb *urb,
+static int uhci_submit_interrupt(struct uhci_hcd *uhci, struct urb *urb,
struct uhci_qh *qh)
{
+ int exponent;
+
/* USB 1.1 interrupt transfers only involve one packet per interval.
* Drivers can submit URBs of any length, but longer ones will need
* multiple intervals to complete.
*/
- qh->skel = uhci->skelqh[__interval_to_skel(urb->interval)];
+
+ /* Figure out which power-of-two queue to use */
+ for (exponent = 7; exponent >= 0; --exponent) {
+ if ((1 << exponent) <= urb->interval)
+ break;
+ }
+ if (exponent < 0)
+ return -EINVAL;
+ urb->interval = 1 << exponent;
+
+ if (qh->period == 0)
+ qh->skel = uhci->skelqh[UHCI_SKEL_INDEX(exponent)];
+ else if (qh->period != urb->interval)
+ return -EINVAL; /* Can't change the period */
+
return uhci_submit_common(uhci, urb, qh);
}
@@ -937,31 +954,50 @@ static int uhci_submit_isochronous(struct uhci_hcd *uhci, struct urb *urb,
unsigned long destination, status;
struct urb_priv *urbp = (struct urb_priv *) urb->hcpriv;
- if (urb->number_of_packets > 900) /* 900? Why? */
+ /* Values must not be too big (could overflow below) */
+ if (urb->interval >= UHCI_NUMFRAMES ||
+ urb->number_of_packets >= UHCI_NUMFRAMES)
return -EFBIG;
- status = TD_CTRL_ACTIVE | TD_CTRL_IOS;
- destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid(urb->pipe);
+ /* Check the period and figure out the starting frame number */
+ uhci_get_current_frame_number(uhci);
+ if (qh->period == 0) {
+ if (urb->transfer_flags & URB_ISO_ASAP) {
+ urb->start_frame = uhci->frame_number + 10;
+ } else {
+ i = urb->start_frame - uhci->frame_number;
+ if (i <= 0 || i >= UHCI_NUMFRAMES)
+ return -EINVAL;
+ }
+ } else if (qh->period != urb->interval) {
+ return -EINVAL; /* Can't change the period */
- /* Figure out the starting frame number */
- if (urb->transfer_flags & URB_ISO_ASAP) {
+ } else { /* Pick up where the last URB leaves off */
if (list_empty(&qh->queue)) {
- uhci_get_current_frame_number(uhci);
- urb->start_frame = (uhci->frame_number + 10);
-
- } else { /* Go right after the last one */
- struct urb *last_urb;
+ frame = uhci->frame_number + 10;
+ } else {
+ struct urb *lurb;
- last_urb = list_entry(qh->queue.prev,
+ lurb = list_entry(qh->queue.prev,
struct urb_priv, node)->urb;
- urb->start_frame = (last_urb->start_frame +
- last_urb->number_of_packets *
- last_urb->interval);
+ frame = lurb->start_frame +
+ lurb->number_of_packets *
+ lurb->interval;
}
- } else {
+ if (urb->transfer_flags & URB_ISO_ASAP)
+ urb->start_frame = frame;
/* FIXME: Sanity check */
}
+ /* Make sure we won't have to go too far into the future */
+ if (uhci_frame_before_eq(uhci->frame_number + UHCI_NUMFRAMES,
+ urb->start_frame + urb->number_of_packets *
+ urb->interval))
+ return -EFBIG;
+
+ status = TD_CTRL_ACTIVE | TD_CTRL_IOS;
+ destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid(urb->pipe);
+
for (i = 0; i < urb->number_of_packets; i++) {
td = uhci_alloc_td(uhci);
if (!td)
@@ -978,6 +1014,7 @@ static int uhci_submit_isochronous(struct uhci_hcd *uhci, struct urb *urb,
td->status |= __constant_cpu_to_le32(TD_CTRL_IOC);
qh->skel = uhci->skel_iso_qh;
+ qh->period = urb->interval;
/* Add the TDs to the frame list */
frame = urb->start_frame;
@@ -1206,6 +1243,7 @@ __acquires(uhci->lock)
uhci_unlink_qh(uhci, qh);
/* Bandwidth stuff not yet implemented */
+ qh->period = 0;
}
}