USB: add queued-unlinks test case to usbtest driver

This patch (as1452b) adds a new test case to the usbtest driver.  Test
24 exercises the unlink-from-queue pathways in the host.  It queues a
user-specified number of bulk-OUT URBs of user-specified size, unlinks
the fourth- and second-from-last URBs in the queue, and then waits to
see if all the URBs complete in the expected way (except of course
that the unlinked URBs might complete normally, if they weren't
unlinked soon enough).

This new test has confirmed the existence of a bug in the ehci-hcd
driver, to be fixed by a separate patch.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
Alan Stern 2011-04-14 11:21:04 -04:00 коммит произвёл Greg Kroah-Hartman
Родитель 1f594b64a4
Коммит 869410f82c
1 изменённых файлов: 118 добавлений и 2 удалений

Просмотреть файл

@ -1195,6 +1195,104 @@ static int unlink_simple(struct usbtest_dev *dev, int pipe, int len)
/*-------------------------------------------------------------------------*/
struct queued_ctx {
struct completion complete;
atomic_t pending;
unsigned num;
int status;
struct urb **urbs;
};
static void unlink_queued_callback(struct urb *urb)
{
int status = urb->status;
struct queued_ctx *ctx = urb->context;
if (ctx->status)
goto done;
if (urb == ctx->urbs[ctx->num - 4] || urb == ctx->urbs[ctx->num - 2]) {
if (status == -ECONNRESET)
goto done;
/* What error should we report if the URB completed normally? */
}
if (status != 0)
ctx->status = status;
done:
if (atomic_dec_and_test(&ctx->pending))
complete(&ctx->complete);
}
static int unlink_queued(struct usbtest_dev *dev, int pipe, unsigned num,
unsigned size)
{
struct queued_ctx ctx;
struct usb_device *udev = testdev_to_usbdev(dev);
void *buf;
dma_addr_t buf_dma;
int i;
int retval = -ENOMEM;
init_completion(&ctx.complete);
atomic_set(&ctx.pending, 1); /* One more than the actual value */
ctx.num = num;
ctx.status = 0;
buf = usb_alloc_coherent(udev, size, GFP_KERNEL, &buf_dma);
if (!buf)
return retval;
memset(buf, 0, size);
/* Allocate and init the urbs we'll queue */
ctx.urbs = kcalloc(num, sizeof(struct urb *), GFP_KERNEL);
if (!ctx.urbs)
goto free_buf;
for (i = 0; i < num; i++) {
ctx.urbs[i] = usb_alloc_urb(0, GFP_KERNEL);
if (!ctx.urbs[i])
goto free_urbs;
usb_fill_bulk_urb(ctx.urbs[i], udev, pipe, buf, size,
unlink_queued_callback, &ctx);
ctx.urbs[i]->transfer_dma = buf_dma;
ctx.urbs[i]->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
}
/* Submit all the URBs and then unlink URBs num - 4 and num - 2. */
for (i = 0; i < num; i++) {
atomic_inc(&ctx.pending);
retval = usb_submit_urb(ctx.urbs[i], GFP_KERNEL);
if (retval != 0) {
dev_err(&dev->intf->dev, "submit urbs[%d] fail %d\n",
i, retval);
atomic_dec(&ctx.pending);
ctx.status = retval;
break;
}
}
if (i == num) {
usb_unlink_urb(ctx.urbs[num - 4]);
usb_unlink_urb(ctx.urbs[num - 2]);
} else {
while (--i >= 0)
usb_unlink_urb(ctx.urbs[i]);
}
if (atomic_dec_and_test(&ctx.pending)) /* The extra count */
complete(&ctx.complete);
wait_for_completion(&ctx.complete);
retval = ctx.status;
free_urbs:
for (i = 0; i < num; i++)
usb_free_urb(ctx.urbs[i]);
kfree(ctx.urbs);
free_buf:
usb_free_coherent(udev, size, buf, buf_dma);
return retval;
}
/*-------------------------------------------------------------------------*/
static int verify_not_halted(struct usbtest_dev *tdev, int ep, struct urb *urb)
{
int retval;
@ -1970,8 +2068,6 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf)
dev->in_iso_pipe, dev->iso_in, 0);
break;
/* FIXME unlink from queue (ring with N urbs) */
/* FIXME scatterlist cancel (needs helper thread) */
/* Tests for bulk I/O using DMA mapping by core and odd address */
@ -2064,6 +2160,26 @@ usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf)
dev->in_iso_pipe, dev->iso_in, 1);
break;
/* unlink URBs from a bulk-OUT queue */
case 24:
if (dev->out_pipe == 0 || !param->length || param->sglen < 4)
break;
retval = 0;
dev_info(&intf->dev, "TEST 17: unlink from %d queues of "
"%d %d-byte writes\n",
param->iterations, param->sglen, param->length);
for (i = param->iterations; retval == 0 && i > 0; --i) {
retval = unlink_queued(dev, dev->out_pipe,
param->sglen, param->length);
if (retval) {
dev_err(&intf->dev,
"unlink queued writes failed %d, "
"iterations left %d\n", retval, i);
break;
}
}
break;
}
do_gettimeofday(&param->duration);
param->duration.tv_sec -= start.tv_sec;