📄 usbtest.c
字号:
#include <linux/config.h>#include <linux/kernel.h>#include <linux/errno.h>#include <linux/init.h>#include <linux/slab.h>#include <linux/mm.h>#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/scatterlist.h>#include <linux/usb.h>/*-------------------------------------------------------------------------*/// FIXME make these public somewhere; usbdevfs.h?//struct usbtest_param { // inputs unsigned test_num; /* 0..(TEST_CASES-1) */ unsigned iterations; unsigned length; unsigned vary; unsigned sglen; // outputs struct timeval duration;};#define USBTEST_REQUEST _IOWR('U', 100, struct usbtest_param)/*-------------------------------------------------------------------------*/#define GENERIC /* let probe() bind using module params *//* Some devices that can be used for testing will have "real" drivers. * Entries for those need to be enabled here by hand, after disabling * that "real" driver. *///#define IBOT2 /* grab iBOT2 webcams *///#define KEYSPAN_19Qi /* grab un-renumerated serial adapter *//*-------------------------------------------------------------------------*/struct usbtest_info { const char *name; u8 ep_in; /* bulk/intr source */ u8 ep_out; /* bulk/intr sink */ unsigned autoconf : 1; unsigned ctrl_out : 1; unsigned iso : 1; /* try iso in/out */ int alt;};/* this is accessed only through usbfs ioctl calls. * one ioctl to issue a test ... one lock per device. * tests create other threads if they need them. * urbs and buffers are allocated dynamically, * and data generated deterministically. */struct usbtest_dev { struct usb_interface *intf; struct usbtest_info *info; int in_pipe; int out_pipe; int in_iso_pipe; int out_iso_pipe; struct usb_endpoint_descriptor *iso_in, *iso_out; struct semaphore sem;#define TBUF_SIZE 256 u8 *buf;};static struct usb_device *testdev_to_usbdev (struct usbtest_dev *test){ return interface_to_usbdev (test->intf);}/* set up all urbs so they can be used with either bulk or interrupt */#define INTERRUPT_RATE 1 /* msec/transfer */#define xprintk(tdev,level,fmt,args...) \ dev_printk(level , &(tdev)->intf->dev , fmt , ## args)#ifdef DEBUG#define DBG(dev,fmt,args...) \ xprintk(dev , KERN_DEBUG , fmt , ## args)#else#define DBG(dev,fmt,args...) \ do { } while (0)#endif /* DEBUG */#ifdef VERBOSE#define VDBG DBG#else#define VDBG(dev,fmt,args...) \ do { } while (0)#endif /* VERBOSE */#define ERROR(dev,fmt,args...) \ xprintk(dev , KERN_ERR , fmt , ## args)#define WARN(dev,fmt,args...) \ xprintk(dev , KERN_WARNING , fmt , ## args)#define INFO(dev,fmt,args...) \ xprintk(dev , KERN_INFO , fmt , ## args)/*-------------------------------------------------------------------------*/static intget_endpoints (struct usbtest_dev *dev, struct usb_interface *intf){ int tmp; struct usb_host_interface *alt; struct usb_host_endpoint *in, *out; struct usb_host_endpoint *iso_in, *iso_out; struct usb_device *udev; for (tmp = 0; tmp < intf->num_altsetting; tmp++) { unsigned ep; in = out = NULL; iso_in = iso_out = NULL; alt = intf->altsetting + tmp; /* take the first altsetting with in-bulk + out-bulk; * ignore other endpoints and altsetttings. */ for (ep = 0; ep < alt->desc.bNumEndpoints; ep++) { struct usb_host_endpoint *e; e = alt->endpoint + ep; switch (e->desc.bmAttributes) { case USB_ENDPOINT_XFER_BULK: break; case USB_ENDPOINT_XFER_ISOC: if (dev->info->iso) goto try_iso; // FALLTHROUGH default: continue; } if (e->desc.bEndpointAddress & USB_DIR_IN) { if (!in) in = e; } else { if (!out) out = e; } continue;try_iso: if (e->desc.bEndpointAddress & USB_DIR_IN) { if (!iso_in) iso_in = e; } else { if (!iso_out) iso_out = e; } } if ((in && out) || (iso_in && iso_out)) goto found; } return -EINVAL;found: udev = testdev_to_usbdev (dev); if (alt->desc.bAlternateSetting != 0) { tmp = usb_set_interface (udev, alt->desc.bInterfaceNumber, alt->desc.bAlternateSetting); if (tmp < 0) return tmp; } if (in) { dev->in_pipe = usb_rcvbulkpipe (udev, in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); dev->out_pipe = usb_sndbulkpipe (udev, out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); } if (iso_in) { dev->iso_in = &iso_in->desc; dev->in_iso_pipe = usb_rcvisocpipe (udev, iso_in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); dev->iso_out = &iso_out->desc; dev->out_iso_pipe = usb_sndisocpipe (udev, iso_out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); } return 0;}/*-------------------------------------------------------------------------*//* Support for testing basic non-queued I/O streams. * * These just package urbs as requests that can be easily canceled. * Each urb's data buffer is dynamically allocated; callers can fill * them with non-zero test data (or test for it) when appropriate. */static void simple_callback (struct urb *urb, struct pt_regs *regs){ complete ((struct completion *) urb->context);}static struct urb *simple_alloc_urb ( struct usb_device *udev, int pipe, unsigned long bytes){ struct urb *urb; if (bytes < 0) return NULL; urb = usb_alloc_urb (0, SLAB_KERNEL); if (!urb) return urb; usb_fill_bulk_urb (urb, udev, pipe, NULL, bytes, simple_callback, NULL); urb->interval = (udev->speed == USB_SPEED_HIGH) ? (INTERRUPT_RATE << 3) : INTERRUPT_RATE; urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; if (usb_pipein (pipe)) urb->transfer_flags |= URB_SHORT_NOT_OK; urb->transfer_buffer = usb_buffer_alloc (udev, bytes, SLAB_KERNEL, &urb->transfer_dma); if (!urb->transfer_buffer) { usb_free_urb (urb); urb = NULL; } else memset (urb->transfer_buffer, 0, bytes); return urb;}static unsigned pattern = 0;module_param (pattern, uint, S_IRUGO);// MODULE_PARM_DESC (pattern, "i/o pattern (0 == zeroes)");static inline void simple_fill_buf (struct urb *urb){ unsigned i; u8 *buf = urb->transfer_buffer; unsigned len = urb->transfer_buffer_length; switch (pattern) { default: // FALLTHROUGH case 0: memset (buf, 0, len); break; case 1: /* mod63 */ for (i = 0; i < len; i++) *buf++ = (u8) (i % 63); break; }}static inline int simple_check_buf (struct urb *urb){ unsigned i; u8 expected; u8 *buf = urb->transfer_buffer; unsigned len = urb->actual_length; for (i = 0; i < len; i++, buf++) { switch (pattern) { /* all-zeroes has no synchronization issues */ case 0: expected = 0; break; /* mod63 stays in sync with short-terminated transfers, * or otherwise when host and gadget agree on how large * each usb transfer request should be. resync is done * with set_interface or set_config. */ case 1: /* mod63 */ expected = i % 63; break; /* always fail unsupported patterns */ default: expected = !*buf; break; } if (*buf == expected) continue; dbg ("buf[%d] = %d (not %d)", i, *buf, expected); return -EINVAL; } return 0;}static void simple_free_urb (struct urb *urb){ usb_buffer_free (urb->dev, urb->transfer_buffer_length, urb->transfer_buffer, urb->transfer_dma); usb_free_urb (urb);}static int simple_io ( struct urb *urb, int iterations, int vary, int expected, const char *label){ struct usb_device *udev = urb->dev; int max = urb->transfer_buffer_length; struct completion completion; int retval = 0; urb->context = &completion; while (retval == 0 && iterations-- > 0) { init_completion (&completion); if (usb_pipeout (urb->pipe)) simple_fill_buf (urb); if ((retval = usb_submit_urb (urb, SLAB_KERNEL)) != 0) break; /* NOTE: no timeouts; can't be broken out of by interrupt */ wait_for_completion (&completion); retval = urb->status; urb->dev = udev; if (retval == 0 && usb_pipein (urb->pipe)) retval = simple_check_buf (urb); if (vary) { int len = urb->transfer_buffer_length; len += vary; len %= max; if (len == 0) len = (vary < max) ? vary : max; urb->transfer_buffer_length = len; } /* FIXME if endpoint halted, clear halt (and log) */ } urb->transfer_buffer_length = max; if (expected != retval) dev_dbg (&udev->dev, "%s failed, iterations left %d, status %d (not %d)\n", label, iterations, retval, expected); return retval;}/*-------------------------------------------------------------------------*//* We use scatterlist primitives to test queued I/O. * Yes, this also tests the scatterlist primitives. */static void free_sglist (struct scatterlist *sg, int nents){ unsigned i; if (!sg) return; for (i = 0; i < nents; i++) { if (!sg [i].page) continue; kfree (page_address (sg [i].page) + sg [i].offset); } kfree (sg);}static struct scatterlist *alloc_sglist (int nents, int max, int vary){ struct scatterlist *sg; unsigned i; unsigned size = max; sg = kmalloc (nents * sizeof *sg, SLAB_KERNEL); if (!sg) return NULL; for (i = 0; i < nents; i++) { char *buf; buf = kmalloc (size, SLAB_KERNEL); if (!buf) { free_sglist (sg, i); return NULL; } memset (buf, 0, size); /* kmalloc pages are always physically contiguous! */ sg_init_one(&sg[i], buf, size); if (vary) { size += vary; size %= max; if (size == 0) size = (vary < max) ? vary : max; } } return sg;}static int perform_sglist ( struct usb_device *udev, unsigned iterations, int pipe, struct usb_sg_request *req, struct scatterlist *sg, int nents){ int retval = 0; while (retval == 0 && iterations-- > 0) { retval = usb_sg_init (req, udev, pipe, (udev->speed == USB_SPEED_HIGH) ? (INTERRUPT_RATE << 3) : INTERRUPT_RATE, sg, nents, 0, SLAB_KERNEL); if (retval) break; usb_sg_wait (req); retval = req->status; /* FIXME if endpoint halted, clear halt (and log) */ } // FIXME for unlink or fault handling tests, don't report // failure if retval is as we expected ... if (retval) dbg ("perform_sglist failed, iterations left %d, status %d", iterations, retval); return retval;}/*-------------------------------------------------------------------------*//* unqueued control message testing * * there's a nice set of device functional requirements in chapter 9 of the * usb 2.0 spec, which we can apply to ANY device, even ones that don't use * special test firmware. * * we know the device is configured (or suspended) by the time it's visible * through usbfs. we can't change that, so we won't test enumeration (which * worked 'well enough' to get here, this time), power management (ditto), * or remote wakeup (which needs human interaction). */static unsigned realworld = 1;module_param (realworld, uint, 0);MODULE_PARM_DESC (realworld, "clear to demand stricter spec compliance");static int get_altsetting (struct usbtest_dev *dev){ struct usb_interface *iface = dev->intf; struct usb_device *udev = interface_to_usbdev (iface); int retval; retval = usb_control_msg (udev, usb_rcvctrlpipe (udev, 0), USB_REQ_GET_INTERFACE, USB_DIR_IN|USB_RECIP_INTERFACE, 0, iface->altsetting [0].desc.bInterfaceNumber, dev->buf, 1, USB_CTRL_GET_TIMEOUT); switch (retval) { case 1: return dev->buf [0]; case 0: retval = -ERANGE; // FALLTHROUGH default: return retval; }}static int set_altsetting (struct usbtest_dev *dev, int alternate){ struct usb_interface *iface = dev->intf; struct usb_device *udev; if (alternate < 0 || alternate >= 256) return -EINVAL; udev = interface_to_usbdev (iface); return usb_set_interface (udev, iface->altsetting [0].desc.bInterfaceNumber, alternate);}static int is_good_config (char *buf, int len){ struct usb_config_descriptor *config; if (len < sizeof *config) return 0; config = (struct usb_config_descriptor *) buf; switch (config->bDescriptorType) { case USB_DT_CONFIG: case USB_DT_OTHER_SPEED_CONFIG: if (config->bLength != 9) { dbg ("bogus config descriptor length"); return 0; } /* this bit 'must be 1' but often isn't */ if (!realworld && !(config->bmAttributes & 0x80)) { dbg ("high bit of config attributes not set"); return 0; } if (config->bmAttributes & 0x1f) { /* reserved == 0 */ dbg ("reserved config bits set"); return 0; } break; default: return 0; } if (le16_to_cpu(config->wTotalLength) == len) /* read it all */ return 1; if (le16_to_cpu(config->wTotalLength) >= TBUF_SIZE) /* max partial read */ return 1; dbg ("bogus config descriptor read size"); return 0;}/* sanity test for standard requests working with usb_control_mesg() and some * of the utility functions which use it. * * this doesn't test how endpoint halts behave or data toggles get set, since * we won't do I/O to bulk/interrupt endpoints here (which is how to change * halt or toggle). toggle testing is impractical without support from hcds. * * this avoids failing devices linux would normally work with, by not testing * config/altsetting operations for devices that only support their defaults. * such devices rarely support those needless operations. *
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -