blob: 96b64bb650910a18a0d38f077f5f43e6814c08e0 [file] [log] [blame]
/*
* 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");