usb: misc: usbtest: add bulk queue test

The bulk queue tests are used to show 'best performance' for bulk
transfer, we are often asked this question by users. The implementation
is the same with iso test, that is queue request at interrupt completion,
so we reuse the iso structures, and rename them as common one.

It's result should be very close to IC simulation, in order
to get that, the device side should also need to prepare enough
queue.

We have got the 'best performance' (IN: 41MB, OUT: 39MB) at i.mx platform
(USB2, ARM Cortex A9, stream mode need to enable) with below command:

Host side:
modprobe usbtest
./testusb -a -t 27 -g 64 -s 16384
./testusb -a -t 28 -g 64 -s 16384
Gadget side:
modprobe g_zero loopdefault=1 qlen=64 buflen=16384

Signed-off-by: Peter Chen <peter.chen@freescale.com>
Cc: Greg KH <gregkh@linuxfoundation.org>
Cc: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Felipe Balbi <balbi@ti.com>
diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c
index ad6dd4a..637f3f7 100644
--- a/drivers/usb/misc/usbtest.c
+++ b/drivers/usb/misc/usbtest.c
@@ -17,6 +17,7 @@
 static int override_alt = -1;
 module_param_named(alt, override_alt, int, 0644);
 MODULE_PARM_DESC(alt, ">= 0 to override altsetting selection");
+static void complicated_callback(struct urb *urb);
 
 /*-------------------------------------------------------------------------*/
 
@@ -239,7 +240,8 @@
 	unsigned long		bytes,
 	unsigned		transfer_flags,
 	unsigned		offset,
-	u8			bInterval)
+	u8			bInterval,
+	usb_complete_t		complete_fn)
 {
 	struct urb		*urb;
 
@@ -248,10 +250,10 @@
 		return urb;
 
 	if (bInterval)
-		usb_fill_int_urb(urb, udev, pipe, NULL, bytes, simple_callback,
+		usb_fill_int_urb(urb, udev, pipe, NULL, bytes, complete_fn,
 				NULL, bInterval);
 	else
-		usb_fill_bulk_urb(urb, udev, pipe, NULL, bytes, simple_callback,
+		usb_fill_bulk_urb(urb, udev, pipe, NULL, bytes, complete_fn,
 				NULL);
 
 	urb->interval = (udev->speed == USB_SPEED_HIGH)
@@ -296,7 +298,17 @@
 	u8			bInterval)
 {
 	return usbtest_alloc_urb(udev, pipe, bytes, URB_NO_TRANSFER_DMA_MAP, 0,
-			bInterval);
+			bInterval, simple_callback);
+}
+
+static struct urb *complicated_alloc_urb(
+	struct usb_device	*udev,
+	int			pipe,
+	unsigned long		bytes,
+	u8			bInterval)
+{
+	return usbtest_alloc_urb(udev, pipe, bytes, URB_NO_TRANSFER_DMA_MAP, 0,
+			bInterval, complicated_callback);
 }
 
 static unsigned pattern;
@@ -1795,12 +1807,12 @@
 
 /*-------------------------------------------------------------------------*/
 
-/* ISO tests ... mimics common usage
+/* ISO/BULK tests ... mimics common usage
  *  - buffer length is split into N packets (mostly maxpacket sized)
  *  - multi-buffers according to sglen
  */
 
-struct iso_context {
+struct transfer_context {
 	unsigned		count;
 	unsigned		pending;
 	spinlock_t		lock;
@@ -1809,11 +1821,12 @@
 	unsigned long		errors;
 	unsigned long		packet_count;
 	struct usbtest_dev	*dev;
+	bool			is_iso;
 };
 
-static void iso_callback(struct urb *urb)
+static void complicated_callback(struct urb *urb)
 {
-	struct iso_context	*ctx = urb->context;
+	struct transfer_context	*ctx = urb->context;
 
 	spin_lock(&ctx->lock);
 	ctx->count--;
@@ -1822,7 +1835,7 @@
 	if (urb->error_count > 0)
 		ctx->errors += urb->error_count;
 	else if (urb->status != 0)
-		ctx->errors += urb->number_of_packets;
+		ctx->errors += (ctx->is_iso ? urb->number_of_packets : 1);
 	else if (urb->actual_length != urb->transfer_buffer_length)
 		ctx->errors++;
 	else if (check_guard_bytes(ctx->dev, urb) != 0)
@@ -1909,7 +1922,7 @@
 		urb->iso_frame_desc[i].offset = maxp * i;
 	}
 
-	urb->complete = iso_callback;
+	urb->complete = complicated_callback;
 	/* urb->context = SET BY CALLER */
 	urb->interval = 1 << (desc->bInterval - 1);
 	urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
@@ -1917,10 +1930,10 @@
 }
 
 static int
-test_iso_queue(struct usbtest_dev *dev, struct usbtest_param *param,
+test_queue(struct usbtest_dev *dev, struct usbtest_param *param,
 		int pipe, struct usb_endpoint_descriptor *desc, unsigned offset)
 {
-	struct iso_context	context;
+	struct transfer_context	context;
 	struct usb_device	*udev;
 	unsigned		i;
 	unsigned long		packets = 0;
@@ -1930,20 +1943,20 @@
 	memset(&context, 0, sizeof(context));
 	context.count = param->iterations * param->sglen;
 	context.dev = dev;
+	context.is_iso = !!desc;
 	init_completion(&context.done);
 	spin_lock_init(&context.lock);
 
 	udev = testdev_to_usbdev(dev);
-	dev_info(&dev->intf->dev,
-		"iso period %d %sframes, wMaxPacket %d, transactions: %d\n",
-		1 << (desc->bInterval - 1),
-		(udev->speed == USB_SPEED_HIGH) ? "micro" : "",
-		usb_endpoint_maxp(desc) & 0x7ff,
-		1 + (0x3 & (usb_endpoint_maxp(desc) >> 11)));
 
 	for (i = 0; i < param->sglen; i++) {
-		urbs[i] = iso_alloc_urb(udev, pipe, desc,
+		if (context.is_iso)
+			urbs[i] = iso_alloc_urb(udev, pipe, desc,
 					param->length, offset);
+		else
+			urbs[i] = complicated_alloc_urb(udev, pipe,
+					param->length, 0);
+
 		if (!urbs[i]) {
 			status = -ENOMEM;
 			goto fail;
@@ -1952,11 +1965,21 @@
 		urbs[i]->context = &context;
 	}
 	packets *= param->iterations;
-	dev_info(&dev->intf->dev,
-		"total %lu msec (%lu packets)\n",
-		(packets * (1 << (desc->bInterval - 1)))
-			/ ((udev->speed == USB_SPEED_HIGH) ? 8 : 1),
-		packets);
+
+	if (context.is_iso) {
+		dev_info(&dev->intf->dev,
+			"iso period %d %sframes, wMaxPacket %d, transactions: %d\n",
+			1 << (desc->bInterval - 1),
+			(udev->speed == USB_SPEED_HIGH) ? "micro" : "",
+			usb_endpoint_maxp(desc) & 0x7ff,
+			1 + (0x3 & (usb_endpoint_maxp(desc) >> 11)));
+
+		dev_info(&dev->intf->dev,
+			"total %lu msec (%lu packets)\n",
+			(packets * (1 << (desc->bInterval - 1)))
+				/ ((udev->speed == USB_SPEED_HIGH) ? 8 : 1),
+			packets);
+	}
 
 	spin_lock_irq(&context.lock);
 	for (i = 0; i < param->sglen; i++) {
@@ -1993,7 +2016,8 @@
 		;
 	else if (context.submit_error)
 		status = -EACCES;
-	else if (context.errors > context.packet_count / 10)
+	else if (context.errors >
+			(context.is_iso ? context.packet_count / 10 : 0))
 		status = -EIO;
 	return status;
 
@@ -2014,8 +2038,8 @@
 	const char *label)
 {
 	int retval;
-	struct urb *urb = usbtest_alloc_urb(
-		testdev_to_usbdev(tdev), pipe, length, transfer_flags, 1, 0);
+	struct urb *urb = usbtest_alloc_urb(testdev_to_usbdev(tdev),
+			pipe, length, transfer_flags, 1, 0, simple_callback);
 
 	if (!urb)
 		return -ENOMEM;
@@ -2342,7 +2366,7 @@
 				param->iterations,
 				param->sglen, param->length);
 		/* FIRMWARE:  iso sink */
-		retval = test_iso_queue(dev, param,
+		retval = test_queue(dev, param,
 				dev->out_iso_pipe, dev->iso_out, 0);
 		break;
 
@@ -2355,7 +2379,7 @@
 				param->iterations,
 				param->sglen, param->length);
 		/* FIRMWARE:  iso source */
-		retval = test_iso_queue(dev, param,
+		retval = test_queue(dev, param,
 				dev->in_iso_pipe, dev->iso_in, 0);
 		break;
 
@@ -2436,7 +2460,7 @@
 			"TEST 22:  write %d iso odd, %d entries of %d bytes\n",
 				param->iterations,
 				param->sglen, param->length);
-		retval = test_iso_queue(dev, param,
+		retval = test_queue(dev, param,
 				dev->out_iso_pipe, dev->iso_out, 1);
 		break;
 
@@ -2447,7 +2471,7 @@
 			"TEST 23:  read %d iso odd, %d entries of %d bytes\n",
 				param->iterations,
 				param->sglen, param->length);
-		retval = test_iso_queue(dev, param,
+		retval = test_queue(dev, param,
 				dev->in_iso_pipe, dev->iso_in, 1);
 		break;
 
@@ -2504,6 +2528,25 @@
 		retval = simple_io(dev, urb, param->iterations, 0, 0, "test26");
 		simple_free_urb(urb);
 		break;
+	case 27:
+		/* We do performance test, so ignore data compare */
+		if (dev->out_pipe == 0 || param->sglen == 0 || pattern != 0)
+			break;
+		dev_info(&intf->dev,
+			"TEST 27: bulk write %dMbytes\n", (param->iterations *
+			param->sglen * param->length) / (1024 * 1024));
+		retval = test_queue(dev, param,
+				dev->out_pipe, NULL, 0);
+		break;
+	case 28:
+		if (dev->in_pipe == 0 || param->sglen == 0 || pattern != 0)
+			break;
+		dev_info(&intf->dev,
+			"TEST 28: bulk read %dMbytes\n", (param->iterations *
+			param->sglen * param->length) / (1024 * 1024));
+		retval = test_queue(dev, param,
+				dev->in_pipe, NULL, 0);
+		break;
 	}
 	do_gettimeofday(&param->duration);
 	param->duration.tv_sec -= start.tv_sec;