DMA Cache problem for control transfers

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,
I've found out why test13 is failing for me on i.MX21. I don't think
it's related to my HCD but I'm not sure if it's an ARM or USB problem :

When the problem occurs I noticed that both the setup phase buffer and
the data phase buffer are in the same 32 byte block.
As dma_get_cache_alignment() returns 32 on this platform I believe they
share a cache line.

So this means that when performing the GET_STATUS request the usb core
is calling:
    dma_map_single(DMA_TO_DEVICE) for the 8 byte setup buffer
    dma_map_single(DMA_FROM_DEVICE) for the 2 byte data buffer

and that when the problem occurs both buffers are in the same cache line..
This causes the data seen in main memory for the data buffer to be
incorrect.

Attached is a patch to demonstrate the problem.
It adds a new usbtest case (#20) which uses the -s parameter to mean
alignment.
It then just does GET_STATUS requests in a loop (without halts or
anything else).

So
./testusb -c 1000 -s 32 -a -t 20   never fails
whereas
./testusb -c 1000 -s 16 -a -t 20   fails within a few iterations

I'm not sure how to fix this - normally the setup buffer and data
buffers are obtained from separate kmallocs - I was just unlucky to get 
nearby addresses.

Regards,
Martin

diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c
index b626283..29fc9aa 100644
--- a/drivers/usb/core/message.c
+++ b/drivers/usb/core/message.c
@@ -950,6 +950,38 @@ int usb_get_status(struct usb_device *dev, int type, int target, void *data)
 }
 EXPORT_SYMBOL_GPL(usb_get_status);
 
+
+int usb_get_status_dma_align_pb(struct usb_device *dev, int type, int target, void *data, int alignment)
+{
+	int ret;
+	struct usb_ctrlrequest *dr;
+	u16 *status;
+	int padding = max(0, (int)(alignment - sizeof(*status)));
+	
+	void *buf = kmalloc(sizeof(*status) + padding + sizeof(struct usb_ctrlrequest), GFP_KERNEL);
+	
+	if (!buf)
+		return -ENOMEM;
+
+	status = (u16*)buf;
+	dr = (buf + sizeof(*status) + padding);
+	
+	printk(KERN_DEBUG "setup %p data %p\n", dr, status);
+	
+	dr->bRequestType = USB_DIR_IN | type;
+	dr->bRequest = USB_REQ_GET_STATUS;
+	dr->wValue = cpu_to_le16(0);
+	dr->wIndex = cpu_to_le16(target);
+	dr->wLength = cpu_to_le16(sizeof(*status));
+
+	ret = usb_internal_control_msg(dev, usb_rcvctrlpipe(dev, 0), dr, status, sizeof(*status), USB_CTRL_GET_TIMEOUT);
+
+	*(u16 *)data = *status;
+	kfree(buf);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(usb_get_status_dma_align_pb);
+
 /**
  * usb_clear_halt - tells device to clear endpoint halt/stall condition
  * @dev: device whose endpoint is halted
diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c
index 5f1a19d..4525e91 100644
--- a/drivers/usb/misc/usbtest.c
+++ b/drivers/usb/misc/usbtest.c
@@ -1540,6 +1540,7 @@ fail:
  * urbs and then call usbtest_disconnect().  To abort a test, you're best
  * off just killing the userspace task and waiting for it to exit.
  */
+extern int usb_get_status_dma_align_pb(struct usb_device *dev, int type, int target, void *data, int alignment);
 
 static int
 usbtest_ioctl (struct usb_interface *intf, unsigned int code, void *buf)
@@ -1853,6 +1854,28 @@ usbtest_ioctl (struct usb_interface *intf, unsigned int code, void *buf)
 
 	// FIXME scatterlist cancel (needs helper thread)
 
+	case 20:
+		if (dev->in_pipe == 0)
+			break;
+		retval = 0;		
+		dev_info(&intf->dev, "TEST 20:  GET_STATUS %d times with %d alignment\n",
+				param->iterations, param->length);
+		
+		for (i = param->iterations; retval >= 0 && i--; /* NOP */) {
+			u16 status;
+			int ep = usb_pipeendpoint (dev->in_pipe) | USB_DIR_IN;
+			retval = usb_get_status_dma_align_pb(udev, USB_RECIP_ENDPOINT, ep, &status, param->length);
+			
+			if (retval >= 0 && status != 0) {
+				ERROR(dev, "ep %02x bogus status: %04x != 0\n", ep, status);
+				retval = -EINVAL;
+			}
+		}
+
+		if (retval < 0)
+			ERROR(dev, "failed %d iterations left %d\n", retval, i);
+		break;
+
 	}
 	do_gettimeofday (&param->duration);
 	param->duration.tv_sec -= start.tv_sec;

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux