Sophie

Sophie

distrib > Scientific%20Linux > 5x > x86_64 > by-pkgid > fc11cd6e1c513a17304da94a5390f3cd > files > 3800

kernel-2.6.18-194.11.1.el5.src.rpm

From: Pete Zaitcev <zaitcev@redhat.com>
Subject: [RHEL 5.1 patch] Strange URBs and running out IOMMU
Date: Tue, 5 Jun 2007 12:56:03 -0700
Bugzilla: 230427
Message-Id: <20070605125603.f7962f68.zaitcev@redhat.com>
Changelog: [usb] Strange URBs and running out IOMMU


IBM found that a system with RSA II adapter runs out of IOMMU when they
run a management application (actually, it runs out swiotlb memory area).
This happens because they submit a Bulk type URB to an Interrupt
endpoint. Surprisingly, this is legal. However, if an application attempts
this, usbfs forgets to fill out the interval field. Host Controller Drivers
(HCDs) verify that values in URB make sense and reject such URBs. Then,
the USB core forgets to free DMA resources.

The main fix is to initialize the interval value to something sensible.
In case of Bulk, we pick the value from the device descriptor. Then,
transfer occurs normally and everyting is fine. This is bug 230427.
I simply backported the fix from upstream.

Also, I'm throwing in the fix for the error path. This is not strictly
required, because Linux leaked DMA resources for years in such circumstances.
I went back to 2001 vintage kernels and found we started leaking when
virt_to_bus() was replaced and never noticed it. But I consider this
a relatively low risk fix, safe enough to get into RHEL 5.1. The bug
is 236922. Upstream does not have this yet, but I have submitted it.

I have built the kernel in Beehive and IBM tested it for functionality.
I also have done some rudimentary tests for regressions (basically, got
my tests sending control URBs down).

Please review and ack as appropriate.

Thanks,
-- Pete

diff -urp -X dontdiff linux-2.6.18-16.el5/drivers/usb/core/devio.c linux-2.6.18-16.el5-230427/drivers/usb/core/devio.c
--- linux-2.6.18-16.el5/drivers/usb/core/devio.c	2007-04-23 15:38:19.000000000 -0700
+++ linux-2.6.18-16.el5-230427/drivers/usb/core/devio.c	2007-04-23 16:31:20.000000000 -0700
@@ -903,7 +903,7 @@ static int proc_do_submiturb(struct dev_
 	struct async *as;
 	struct usb_ctrlrequest *dr = NULL;
 	unsigned int u, totlen, isofrmlen;
-	int ret, interval = 0, ifnum = -1;
+	int ret, ifnum = -1;
 
 	if (uurb->flags & ~(USBDEVFS_URB_ISO_ASAP|USBDEVFS_URB_SHORT_NOT_OK|
 			   URB_NO_FSBR|URB_ZERO_PACKET))
@@ -979,7 +979,6 @@ static int proc_do_submiturb(struct dev_
 		if ((ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
 				!= USB_ENDPOINT_XFER_ISOC)
 			return -EINVAL;
-		interval = 1 << min (15, ep->desc.bInterval - 1);
 		isofrmlen = sizeof(struct usbdevfs_iso_packet_desc) * uurb->number_of_packets;
 		if (!(isopkt = kmalloc(isofrmlen, GFP_KERNEL)))
 			return -ENOMEM;
@@ -1008,10 +1007,6 @@ static int proc_do_submiturb(struct dev_
 		if ((ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
 				!= USB_ENDPOINT_XFER_INT)
 			return -EINVAL;
-		if (ps->dev->speed == USB_SPEED_HIGH)
-			interval = 1 << min (15, ep->desc.bInterval - 1);
-		else
-			interval = ep->desc.bInterval;
 		if (uurb->buffer_length > MAX_USBFS_BUFFER_SIZE)
 			return -EINVAL;
 		if (!access_ok((uurb->endpoint & USB_DIR_IN) ? VERIFY_WRITE : VERIFY_READ, uurb->buffer, uurb->buffer_length))
@@ -1040,7 +1035,11 @@ static int proc_do_submiturb(struct dev_
 	as->urb->setup_packet = (unsigned char*)dr;
 	as->urb->start_frame = uurb->start_frame;
 	as->urb->number_of_packets = uurb->number_of_packets;
-	as->urb->interval = interval;
+	if (uurb->type == USBDEVFS_URB_TYPE_ISO ||
+			ps->dev->speed == USB_SPEED_HIGH)
+		as->urb->interval = 1 << min(15, ep->desc.bInterval - 1);
+	else
+		as->urb->interval = ep->desc.bInterval;
         as->urb->context = as;
         as->urb->complete = async_completed;
 	for (totlen = u = 0; u < uurb->number_of_packets; u++) {
diff -urp -X dontdiff linux-2.6.18-16.el5/drivers/usb/core/hcd.c linux-2.6.18-16.el5-230427/drivers/usb/core/hcd.c
--- linux-2.6.18-16.el5/drivers/usb/core/hcd.c	2006-09-19 20:42:06.000000000 -0700
+++ linux-2.6.18-16.el5-230427/drivers/usb/core/hcd.c	2007-06-01 13:30:38.000000000 -0700
@@ -1090,9 +1090,10 @@ EXPORT_SYMBOL (usb_release_bandwidth);
 
 /*-------------------------------------------------------------------------*/
 
-static void urb_unlink (struct urb *urb)
+static void urb_unlink(struct usb_hcd *hcd, struct urb *urb)
 {
 	unsigned long		flags;
+	int at_root_hub = (urb->dev == hcd->self.root_hub);
 
 	/* Release any periodic transfer bandwidth */
 	if (urb->bandwidth)
@@ -1100,12 +1101,27 @@ static void urb_unlink (struct urb *urb)
 			usb_pipeisoc (urb->pipe));
 
 	/* clear all state linking urb to this dev (and hcd) */
-
 	spin_lock_irqsave (&hcd_data_lock, flags);
 	list_del_init (&urb->urb_list);
 	spin_unlock_irqrestore (&hcd_data_lock, flags);
-}
 
+	/* lower level hcd code should use *_dma exclusively */
+	if (hcd->self.controller->dma_mask && !at_root_hub) {
+		if (usb_pipecontrol (urb->pipe)
+			&& !(urb->transfer_flags & URB_NO_SETUP_DMA_MAP))
+			dma_unmap_single (hcd->self.controller, urb->setup_dma,
+					sizeof (struct usb_ctrlrequest),
+					DMA_TO_DEVICE);
+		if (urb->transfer_buffer_length != 0
+			&& !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP))
+			dma_unmap_single (hcd->self.controller, 
+					urb->transfer_dma,
+					urb->transfer_buffer_length,
+					usb_pipein (urb->pipe)
+					    ? DMA_FROM_DEVICE
+					    : DMA_TO_DEVICE);
+	}
+}
 
 /* may be called in any context with a valid urb->dev usecount
  * caller surrenders "ownership" of urb
@@ -1208,7 +1224,7 @@ doit:
 	status = hcd->driver->urb_enqueue (hcd, ep, urb, mem_flags);
 done:
 	if (unlikely (status)) {
-		urb_unlink (urb);
+		urb_unlink(hcd, urb);
 		atomic_dec (&urb->use_count);
 		if (urb->reject)
 			wake_up (&usb_kill_urb_queue);
@@ -1612,28 +1628,7 @@ static struct usb_operations usb_hcd_ope
  */
 void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb, struct pt_regs *regs)
 {
-	int at_root_hub;
-
-	at_root_hub = (urb->dev == hcd->self.root_hub);
-	urb_unlink (urb);
-
-	/* lower level hcd code should use *_dma exclusively */
-	if (hcd->self.controller->dma_mask && !at_root_hub) {
-		if (usb_pipecontrol (urb->pipe)
-			&& !(urb->transfer_flags & URB_NO_SETUP_DMA_MAP))
-			dma_unmap_single (hcd->self.controller, urb->setup_dma,
-					sizeof (struct usb_ctrlrequest),
-					DMA_TO_DEVICE);
-		if (urb->transfer_buffer_length != 0
-			&& !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP))
-			dma_unmap_single (hcd->self.controller, 
-					urb->transfer_dma,
-					urb->transfer_buffer_length,
-					usb_pipein (urb->pipe)
-					    ? DMA_FROM_DEVICE
-					    : DMA_TO_DEVICE);
-	}
-
+	urb_unlink(hcd, urb);
 	usbmon_urb_complete (&hcd->self, urb);
 	/* pass ownership to the completion handler */
 	urb->complete (urb, regs);