diff options
author | Preetham Chandru R <pchandru@nvidia.com> | 2012-10-22 15:25:45 +0530 |
---|---|---|
committer | Rohan Somvanshi <rsomvanshi@nvidia.com> | 2012-11-22 10:26:07 -0800 |
commit | 7673b44859c2c97ecb464c9aaadb0bc3d6850215 (patch) | |
tree | 9f656cde98e9cf2d4fa45c35b7850ef1ce8e7ad3 /drivers/usb | |
parent | 6f9b140166b6af54defc1317a821638f8182616c (diff) |
usb:class:nv-usb: driver to measure usb throughput
a class driver to measure usb throughput
Change-Id: Ibadfb7f5722e6969c93064fcf4ca79139fd08728
Signed-off-by: Preetham Chandru R <pchandru@nvidia.com>
Reviewed-on: http://git-master/r/138994
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Venkat Moganty <vmoganty@nvidia.com>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/class/Kconfig | 10 | ||||
-rw-r--r-- | drivers/usb/class/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/class/nv-usb.c | 564 | ||||
-rw-r--r-- | drivers/usb/class/nv-usb.h | 100 |
5 files changed, 676 insertions, 0 deletions
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 53a7bc07dd8d..97a540e9aeaf 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_USB_ACM) += class/ obj-$(CONFIG_USB_PRINTER) += class/ obj-$(CONFIG_USB_WDM) += class/ obj-$(CONFIG_USB_TMC) += class/ +obj-$(CONFIG_USB_KPI) += class/ obj-$(CONFIG_USB_STORAGE) += storage/ obj-$(CONFIG_USB) += storage/ diff --git a/drivers/usb/class/Kconfig b/drivers/usb/class/Kconfig index 2519e320098f..d63944903cc3 100644 --- a/drivers/usb/class/Kconfig +++ b/drivers/usb/class/Kconfig @@ -50,3 +50,13 @@ config USB_TMC To compile this driver as a module, choose M here: the module will be called usbtmc. + +config USB_KPI + tristate "USB KPI Measurement" + depends on USB + help + Say Y here if you want to measure the usb write and read + throughput. + + To compile this driver as a module, choose M here: the + module will be called nv-usb. diff --git a/drivers/usb/class/Makefile b/drivers/usb/class/Makefile index 32e85277b5cf..49d0913a241b 100644 --- a/drivers/usb/class/Makefile +++ b/drivers/usb/class/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_USB_ACM) += cdc-acm.o obj-$(CONFIG_USB_PRINTER) += usblp.o obj-$(CONFIG_USB_WDM) += cdc-wdm.o obj-$(CONFIG_USB_TMC) += usbtmc.o +obj-$(CONFIG_USB_KPI) += nv-usb.o diff --git a/drivers/usb/class/nv-usb.c b/drivers/usb/class/nv-usb.c new file mode 100644 index 000000000000..96b64bb65091 --- /dev/null +++ b/drivers/usb/class/nv-usb.c @@ -0,0 +1,564 @@ +/* + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/kref.h> +#include <linux/uaccess.h> +#include <linux/usb.h> +#include <linux/mutex.h> +#include <linux/time.h> + +#include "nv-usb.h" + +#ifdef DEBUG +#define INFO(stuff...) pr_info("nv-usb: " stuff) +#else +#define INFO(stuff...) do {} while (0) +#endif + + +#define NV_USB_BULK_VENDOR_ID 0x0955 +#define NV_USB_BULK_PRODUCT_ID 0xffff + +/* table of devices that work with this driver */ +static const struct usb_device_id nv_usb_table[] = { + { USB_DEVICE(NV_USB_BULK_VENDOR_ID, NV_USB_BULK_PRODUCT_ID) }, + {} +}; + +MODULE_DEVICE_TABLE(usb, nv_usb_table); + +/* Need to check if the below minor id is ok */ +#define NV_USB_BULK_MINOR_BASE 192 + +static struct usb_driver nv_usb_driver; + +static void nv_usb_delete(struct kref *kref) +{ + struct nv_usb *dev = to_nv_usb_dev(kref); + + usb_put_dev(dev->udev); +} + +static int nv_usb_open(struct inode *inode, struct file *file) +{ + struct nv_usb *dev; + struct usb_interface *interface; + int subminor; + + subminor = iminor(inode); + + interface = usb_find_interface(&nv_usb_driver, subminor); + if (interface == NULL) { + INFO("%s(%d) failed to get interface\n", __func__, __LINE__); + return -ENODEV; + } + + dev = usb_get_intfdata(interface); + if (dev == NULL) { + INFO("%s(%d) failed to get interface\n", __func__, __LINE__); + return -ENODEV; + } + + kref_get(&dev->kref); + + mutex_lock(&dev->mutex); + file->private_data = dev; + mutex_unlock(&dev->mutex); + + return 0; +} + +static int nv_usb_release(struct inode *inode, struct file *file) +{ + struct nv_usb *dev; + + dev = file->private_data; + if (dev == NULL) { + INFO("%s(%d) failed to get interface\n", __func__, __LINE__); + return -ENODEV; + } + + kref_put(&dev->kref, nv_usb_delete); + return 0; +} + +static int nv_usb_flush(struct file *file, fl_owner_t id) +{ + return 0; +} + +static int nv_usb_transfer(struct bulk_data *data); + +static ssize_t nv_usb_read(struct file *file, char *buffer, size_t count, + loff_t *ppos) +{ + return 0; +} + +unsigned long +nv_usb_calc_time(struct timeval g_sttime, struct timeval g_entime) +{ + + unsigned int num_sec = g_entime.tv_sec - g_sttime.tv_sec; + + if (num_sec) + return (g_entime.tv_usec + + (1000000 - g_sttime.tv_usec) + + ((num_sec - 1) * 1000000)); + else + return g_entime.tv_usec - g_sttime.tv_usec; + +} + +static void nv_usb_callback(struct urb *urb) +{ + struct completion *urb_done_ptr = urb->context; + + complete(urb_done_ptr); +} + +static int nv_usb_transfer(struct bulk_data *data) +{ + struct nv_usb *dev = data->dev; + struct nvusb_cb_wrap *bcb = NULL; + struct nvusb_cs_wrap *bcs = NULL; + struct urb *urb = NULL; + unsigned int transfer_length = data->length; + struct completion urb_done; + int retval = 0; + unsigned int cbwlen = US_BULK_CB_WRAP_LEN; + unsigned int cswlen = US_BULK_CS_WRAP_LEN; + char *buf = NULL; + struct timeval g_sttime, g_entime; + unsigned long data_transfer_time = 0; + + init_completion(&urb_done); + + mutex_lock(&dev->mutex); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + INFO("%s(%d) failed to alloc urb\n", __func__, __LINE__); + return -ENOMEM; + } + + bcb = usb_alloc_coherent(dev->udev, cbwlen, GFP_KERNEL, + &urb->transfer_dma); + + if (!bcb) { + INFO("%s(%d) failed to alloc bcb\n", __func__, __LINE__); + retval = -ENOMEM; + goto out; + } + + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = cpu_to_le32(transfer_length); + /*0 --> write 1 --> read*/ + bcb->Flags = data->data_direction ? 1 << 7 : 0; + bcb->Tag = ++dev->tag; + bcb->Length = data->sub_cmd_length; + + memset(bcb->CDB, 0, sizeof(bcb->CDB)); + memcpy(bcb->CDB, data->sub_cmd, bcb->Length); + + if (!dev->interface) { + INFO("%s(%d) interface disconnected\n", __func__, __LINE__); + usb_free_coherent(dev->udev, cbwlen, bcb, urb->transfer_dma); + retval = -ENODEV; + goto out; + } + + INFO("cbwlen = %d\n", cbwlen); + usb_fill_bulk_urb(urb, dev->udev, + usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr), + bcb, cbwlen, nv_usb_callback, dev); + + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + urb->context = &urb_done; + retval = usb_submit_urb(urb, GFP_KERNEL); + + if (retval) { + usb_free_coherent(dev->udev, cbwlen, bcb, urb->transfer_dma); + INFO("%s(%d) urb submit failed\n", __func__, __LINE__); + goto out; + } + + wait_for_completion_interruptible_timeout( + &urb_done, 20000); + + + usb_free_coherent(dev->udev, cbwlen, bcb, urb->transfer_dma); + INFO("%s(%d)\n", __func__, __LINE__); + + + if (transfer_length) { + INFO("%s(%d) Transfering Data = %d\n", + __func__, __LINE__, transfer_length); + buf = usb_alloc_coherent(dev->udev, transfer_length, GFP_KERNEL, + &urb->transfer_dma); + + if (!buf) { + INFO("%s(%d)failed to alloc buffer for data transfer\n", + __func__, __LINE__); + retval = -ENOMEM; + goto out; + } + + if (!data->data_direction) { + memset(buf, data->write_char, transfer_length); + usb_fill_bulk_urb(urb, dev->udev, + usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr), + buf, transfer_length, nv_usb_callback, dev); + } else { + memset(buf, 0, transfer_length); + usb_fill_bulk_urb(urb, dev->udev, + usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr), + buf, transfer_length, nv_usb_callback, dev); + } + + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + urb->context = &urb_done; + + do_gettimeofday(&g_sttime); + retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval) { + usb_free_coherent(dev->udev, transfer_length, buf, + urb->transfer_dma); + INFO("%s(%d) urb submit failed\n", __func__, __LINE__); + goto out; + } + + wait_for_completion_interruptible_timeout( + &urb_done, 20000); + do_gettimeofday(&g_entime); + if (data->data_direction) + memcpy(data->buf, buf , transfer_length); + + usb_free_coherent(dev->udev, + transfer_length, buf, urb->transfer_dma); + data_transfer_time = nv_usb_calc_time(g_sttime, g_entime); + } + + bcs = usb_alloc_coherent(dev->udev, cswlen, GFP_KERNEL, + &urb->transfer_dma); + if (!bcs) { + INFO("%s(%d) failed to alloc bcs\n", __func__, __LINE__); + retval = -ENOMEM; + goto out; + } + memset(bcs, 0, cswlen); + usb_fill_bulk_urb(urb, dev->udev, + usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr), + bcs, cswlen, nv_usb_callback, dev); + + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + urb->context = &urb_done; + retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval) { + usb_free_coherent(dev->udev, cswlen, bcs, urb->transfer_dma); + INFO("%s(%d) urb submit failed\n", __func__, __LINE__); + goto out; + } + + wait_for_completion_interruptible_timeout( + &urb_done, 20000); + + INFO( + "%s(%d) dCSWSignature = %x dCSWTag = %x Dataresidur = %d status = %d\n", + __func__, __LINE__, bcs->Signature, + bcs->Tag, bcs->Residue, bcs->Status); + + if (bcs->Status) + data_transfer_time = 0; + + INFO("time taken is %lu\n", data_transfer_time); + + data->data_transfer_time = data_transfer_time; + data->g_data_transfer_time = bcs->Residue; + + usb_free_coherent(dev->udev, cswlen, bcs, urb->transfer_dma); + usb_free_urb(urb); + mutex_unlock(&dev->mutex); + return 0; +out: + usb_free_urb(urb); + mutex_unlock(&dev->mutex); + return retval; +} + +static ssize_t nv_usb_write(struct file *file, const char *user_buffer, + size_t count, loff_t *ppos) +{ + return count; +} + +static long nv_usb_ioctl(struct file *file, unsigned int cmd_in, + unsigned long arg, struct nv_usb *dev) +{ + void __user *p = (void __user *)arg; + struct bulk_data *data = NULL; + struct user_bulk_data *user_data = NULL; + int ret = 0; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + INFO("%s(%d) No Memory\n", __func__, __LINE__); + return -ENOMEM; + } + + user_data = kzalloc(sizeof(*user_data), GFP_KERNEL); + if (!user_data) { + INFO("%s(%d) No Memory\n", __func__, __LINE__); + kfree(data); + return -ENOMEM; + } + + if (__copy_from_user(user_data, p, sizeof(*user_data))) { + INFO("%s(%d) copy from user failed\n", __func__, __LINE__); + kfree(user_data); + kfree(data); + return -EFAULT; + } + + data->dev = file->private_data; + data->sub_cmd_length = user_data->sub_cmd_length; + + data->sub_cmd = kzalloc(data->sub_cmd_length, GFP_KERNEL); + if (!data->sub_cmd) { + INFO("%s(%d) No Memory\n", __func__, __LINE__); + kfree(user_data); + kfree(data); + return -ENOMEM; + } + if (__copy_from_user(data->sub_cmd, + user_data->sub_cmd, data->sub_cmd_length)) { + INFO("%s(%d) copy from user failed\n", __func__, __LINE__); + kfree(data->sub_cmd); + kfree(user_data); + kfree(data); + return -EFAULT; + } + + data->length = user_data->length; + data->buf = kzalloc(data->length, GFP_KERNEL); + if (!data->buf) { + INFO("%s(%d) No Memory\n", __func__, __LINE__); + kfree(data->sub_cmd); + kfree(user_data); + kfree(data); + return -ENOMEM; + } + + data->write_char = user_data->write_char; + + switch (cmd_in) { + case NVUSB_BULK_WRITE: + if (__copy_from_user(data->buf, user_data->buf, data->length)) { + INFO("%s(%d) copy from user failed\n", + __func__, __LINE__); + ret = -EFAULT; + goto error; + } + data->data_direction = 0; + ret = nv_usb_transfer(data); + if (ret) { + INFO("%s(%d) nv_usb_transfer failed\n", + __func__, __LINE__); + goto error; + } + break; + case NVUSB_BULK_READ: + data->data_direction = 1; + INFO("%s(%d)\n", __func__, __LINE__); + ret = nv_usb_transfer(data); + if (ret) { + INFO("%s(%d) nv_usb_transfer failed\n", + __func__, __LINE__); + goto error; + } + + if (__copy_to_user(user_data->buf, data->buf, data->length)) { + INFO("%s(%d) copy to user failed\n", + __func__, __LINE__); + ret = -EFAULT; + goto error; + } + break; + default: + INFO("%s(%d) Invalid ioctl command %x\n", + __func__, __LINE__, cmd_in); + ret = -EINVAL; + goto error; + } + + user_data->data_transfer_time = data->data_transfer_time; + user_data->g_data_transfer_time = data->g_data_transfer_time; + + if (copy_to_user(p, user_data, sizeof(*user_data))) { + INFO("%s(%d) copy to user failed\n", __func__, __LINE__); + ret = -EFAULT; + } + +error: + kfree(data->buf); + kfree(data->sub_cmd); + kfree(user_data); + kfree(data); + + return ret; + +} +static long nv_usb_unlocked_ioctl(struct file *file, unsigned int cmd_in, + unsigned long arg) +{ + int ret; + struct nv_usb *dev; + + dev = file->private_data; + + ret = nv_usb_ioctl(file, cmd_in, arg, dev); + + return ret; +} + +static const struct file_operations nv_usb_fops = { + .owner = THIS_MODULE, + .read = nv_usb_read, + .write = nv_usb_write, + .open = nv_usb_open, + .unlocked_ioctl = nv_usb_unlocked_ioctl, + .release = nv_usb_release, + .flush = nv_usb_flush, + .llseek = noop_llseek, +}; + +static struct usb_class_driver nv_usb_class = { + .name = "nvbulk%d", + .fops = &nv_usb_fops, + .minor_base = NV_USB_BULK_MINOR_BASE, +}; + +static int nv_usb_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + struct nv_usb *dev; + struct usb_device *udev; + int retval = 0; + int i; + + udev = usb_get_dev(interface_to_usbdev(interface)); + /* allocate memory for our device state and initialize it */ + dev = devm_kzalloc(&udev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) { + INFO("%s(%d) Out of Memory\n", __func__, __LINE__); + return -ENOMEM; + } + + kref_init(&dev->kref); + init_usb_anchor(&dev->submitted); + mutex_init(&dev->mutex); + + dev->udev = udev; + dev->interface = interface; + + /*Set up the Endpoint Information */ + /* use only the first bulk-in and bulk-out endpoints*/ + iface_desc = interface->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_is_bulk_in(endpoint)) { + /*We found a bulk_in endpoint */ + dev->bulk_in_size = usb_endpoint_maxp(endpoint); + dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; + } + + if (usb_endpoint_is_bulk_out(endpoint)) { + /*We found a buld_out endpoint */ + dev->bulk_out_endpointAddr = endpoint->bEndpointAddress; + } + } + + if (dev->bulk_in_endpointAddr == 0 + || dev->bulk_out_endpointAddr == 0) { + INFO("%s(%d) Count not find bulk point infomations\n", + __func__, __LINE__); + goto error; + } + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + + /* we can register the device now, as it is ready */ + retval = usb_register_dev(interface, &nv_usb_class); + + if (retval) { + INFO("%s(%d) usb_register_dev failed\n", __func__, __LINE__); + usb_set_intfdata(interface, NULL); + goto error; + } + + INFO("%s(%d) device now attached to USB\n", __func__, __LINE__); + return 0; +error: + if (dev) { + kref_put(&dev->kref, nv_usb_delete); + retval = -EFAULT; + } + + return retval; + +} + +static void nv_usb_disconnect(struct usb_interface *interface) +{ + struct nv_usb *dev; + int minor = interface->minor; + + dev = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + + usb_deregister_dev(interface, &nv_usb_class); + + mutex_lock(&dev->mutex); + dev->interface = NULL; + mutex_unlock(&dev->mutex); + + usb_kill_anchored_urbs(&dev->submitted); + + kref_put(&dev->kref, nv_usb_delete); + + INFO("%s(%d) disconnected minor = %d\n", __func__, __LINE__, minor); + +} + +static struct usb_driver nv_usb_driver = { + .name = "nv-usb", + .probe = nv_usb_probe, + .disconnect = nv_usb_disconnect, + .id_table = nv_usb_table, +}; + +module_usb_driver(nv_usb_driver); +MODULE_AUTHOR("NVIDIA"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/class/nv-usb.h b/drivers/usb/class/nv-usb.h new file mode 100644 index 000000000000..83922cc583e8 --- /dev/null +++ b/drivers/usb/class/nv-usb.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __LINUX_USB_NV_USB_BULK_H +#define __LINUX_USB_NV_USB_BULK_H + + +#define NVUSB_BULK_WRITE 0X2000 +#define NVUSB_BULK_READ 0X2001 + +/* command block wrapper */ +struct nvusb_cb_wrap { + __le32 Signature; + __u32 Tag; + __le32 DataTransferLength; + __u8 Flags; + __u8 Length; + __u8 CDB[16]; +}; + +#define US_BULK_CB_WRAP_LEN 31 +#define US_BULK_CB_SIGN 0x43425355 +#define US_BULK_FLAG_IN (1 << 7) +#define US_BULK_FLAG_OUT 0 + +/* command status wrapper */ +struct nvusb_cs_wrap { + __le32 Signature; + __u32 Tag; + __le32 Residue; + __u8 Status; +}; + +#define US_BULK_CS_WRAP_LEN 13 +#define US_BULK_CS_SIGN 0x53425355 +#define US_BULK_STAT_OK 0 +#define US_BULK_STAT_FAIL 1 +#define US_BULK_STAT_BAD_DATA 2 + +struct bulk_data { + struct nv_usb *dev; + __u8 data_direction; + __u8 sub_cmd_length; + __u8 *sub_cmd; + __u32 length; + __u8 *buf; + __u8 write_char; + __u32 data_transfer_time; + __u32 g_data_transfer_time; +}; + +struct user_bulk_data { + __u8 sub_cmd_length; + __u8 __user *sub_cmd; + __u32 length; + __u8 __user *buf; + __u8 write_char; + __u32 data_transfer_time; + __u32 g_data_transfer_time; + +}; + + +/* Structure to hold all of our device specific stuff */ +struct nv_usb { + /* the usb device for this device */ + struct usb_device *udev; + /* the interface for this device */ + struct usb_interface *interface; + /* in case we need to retract our submissions */ + struct usb_anchor submitted; + /* the size of the receive buffer */ + size_t bulk_in_size; + /* the address of the bulk in endpoint */ + __u8 bulk_in_endpointAddr; + /* the address of the bulk out endpoint */ + __u8 bulk_out_endpointAddr; + __u32 tag; + struct kref kref; + struct mutex mutex; +}; +#define to_nv_usb_dev(d) container_of(d, struct nv_usb, kref) + +#endif + + + |