diff options
Diffstat (limited to 'drivers/usb/usbip')
-rw-r--r-- | drivers/usb/usbip/Kconfig | 17 | ||||
-rw-r--r-- | drivers/usb/usbip/Makefile | 3 | ||||
-rw-r--r-- | drivers/usb/usbip/stub.h | 1 | ||||
-rw-r--r-- | drivers/usb/usbip/stub_dev.c | 7 | ||||
-rw-r--r-- | drivers/usb/usbip/stub_rx.c | 19 | ||||
-rw-r--r-- | drivers/usb/usbip/stub_tx.c | 11 | ||||
-rw-r--r-- | drivers/usb/usbip/usbip_common.c | 17 | ||||
-rw-r--r-- | drivers/usb/usbip/usbip_common.h | 14 | ||||
-rw-r--r-- | drivers/usb/usbip/usbip_event.c | 168 | ||||
-rw-r--r-- | drivers/usb/usbip/vhci_hcd.c | 2 | ||||
-rw-r--r-- | drivers/usb/usbip/vudc.h | 190 | ||||
-rw-r--r-- | drivers/usb/usbip/vudc_dev.c | 661 | ||||
-rw-r--r-- | drivers/usb/usbip/vudc_main.c | 113 | ||||
-rw-r--r-- | drivers/usb/usbip/vudc_rx.c | 234 | ||||
-rw-r--r-- | drivers/usb/usbip/vudc_sysfs.c | 229 | ||||
-rw-r--r-- | drivers/usb/usbip/vudc_transfer.c | 506 | ||||
-rw-r--r-- | drivers/usb/usbip/vudc_tx.c | 289 |
17 files changed, 2409 insertions, 72 deletions
diff --git a/drivers/usb/usbip/Kconfig b/drivers/usb/usbip/Kconfig index bd99e9e47e50..17646b25343f 100644 --- a/drivers/usb/usbip/Kconfig +++ b/drivers/usb/usbip/Kconfig @@ -1,6 +1,6 @@ config USBIP_CORE tristate "USB/IP support" - depends on USB && NET + depends on USB_COMMON && NET ---help--- This enables pushing USB packets over IP to allow remote machines direct access to USB devices. It provides the @@ -16,7 +16,7 @@ config USBIP_CORE config USBIP_VHCI_HCD tristate "VHCI hcd" - depends on USBIP_CORE + depends on USBIP_CORE && USB ---help--- This enables the USB/IP virtual host controller driver, which is run on the remote machine. @@ -26,7 +26,7 @@ config USBIP_VHCI_HCD config USBIP_HOST tristate "Host driver" - depends on USBIP_CORE + depends on USBIP_CORE && USB ---help--- This enables the USB/IP host driver, which is run on the machine that is sharing the USB devices. @@ -34,6 +34,17 @@ config USBIP_HOST To compile this driver as a module, choose M here: the module will be called usbip-host. +config USBIP_VUDC + tristate "VUDC driver" + depends on USBIP_CORE && USB_GADGET + ---help--- + This enables the USB/IP virtual USB device controller + driver, which is run on the host machine, allowing the + machine itself to act as a device. + + To compile this driver as a module, choose M here: the + module will be called usbip-vudc. + config USBIP_DEBUG bool "Debug messages for USB/IP" depends on USBIP_CORE diff --git a/drivers/usb/usbip/Makefile b/drivers/usb/usbip/Makefile index 9ecd61545be1..d843a9e68852 100644 --- a/drivers/usb/usbip/Makefile +++ b/drivers/usb/usbip/Makefile @@ -8,3 +8,6 @@ vhci-hcd-y := vhci_sysfs.o vhci_tx.o vhci_rx.o vhci_hcd.o obj-$(CONFIG_USBIP_HOST) += usbip-host.o usbip-host-y := stub_dev.o stub_main.o stub_rx.o stub_tx.o + +obj-$(CONFIG_USBIP_VUDC) += usbip-vudc.o +usbip-vudc-y := vudc_dev.o vudc_sysfs.o vudc_tx.o vudc_rx.o vudc_transfer.o vudc_main.o diff --git a/drivers/usb/usbip/stub.h b/drivers/usb/usbip/stub.h index 266e2b0ce9a8..910f027773aa 100644 --- a/drivers/usb/usbip/stub.h +++ b/drivers/usb/usbip/stub.h @@ -33,7 +33,6 @@ #define STUB_BUSID_ALLOC 3 struct stub_device { - struct usb_interface *interface; struct usb_device *udev; struct usbip_device ud; diff --git a/drivers/usb/usbip/stub_dev.c b/drivers/usb/usbip/stub_dev.c index a3ec49bdc1e6..c653ce533430 100644 --- a/drivers/usb/usbip/stub_dev.c +++ b/drivers/usb/usbip/stub_dev.c @@ -219,7 +219,7 @@ static void stub_device_reset(struct usbip_device *ud) dev_dbg(&udev->dev, "device reset"); - ret = usb_lock_device_for_reset(udev, sdev->interface); + ret = usb_lock_device_for_reset(udev, NULL); if (ret < 0) { dev_err(&udev->dev, "lock for reset\n"); spin_lock_irq(&ud->lock); @@ -252,7 +252,7 @@ static void stub_device_unusable(struct usbip_device *ud) /** * stub_device_alloc - allocate a new stub_device struct - * @interface: usb_interface of a new device + * @udev: usb_device of a new device * * Allocates and initializes a new stub_device struct. */ @@ -388,7 +388,6 @@ err_files: err_port: dev_set_drvdata(&udev->dev, NULL); usb_put_dev(udev); - kthread_stop_put(sdev->ud.eh); busid_priv->sdev = NULL; stub_device_free(sdev); @@ -449,7 +448,7 @@ static void stub_disconnect(struct usb_device *udev) } /* If usb reset is called from event handler */ - if (busid_priv->sdev->ud.eh == current) + if (usbip_in_eh(current)) return; /* shutdown the current connection */ diff --git a/drivers/usb/usbip/stub_rx.c b/drivers/usb/usbip/stub_rx.c index 00e475c51a12..2df63e305722 100644 --- a/drivers/usb/usbip/stub_rx.c +++ b/drivers/usb/usbip/stub_rx.c @@ -165,12 +165,7 @@ static int tweak_reset_device_cmd(struct urb *urb) dev_info(&urb->dev->dev, "usb_queue_reset_device\n"); - /* - * With the implementation of pre_reset and post_reset the driver no - * longer unbinds. This allows the use of synchronous reset. - */ - - if (usb_lock_device_for_reset(sdev->udev, sdev->interface) < 0) { + if (usb_lock_device_for_reset(sdev->udev, NULL) < 0) { dev_err(&urb->dev->dev, "could not obtain lock to reset device\n"); return 0; } @@ -321,7 +316,7 @@ static struct stub_priv *stub_priv_alloc(struct stub_device *sdev, priv = kmem_cache_zalloc(stub_priv_cache, GFP_ATOMIC); if (!priv) { - dev_err(&sdev->interface->dev, "alloc stub_priv\n"); + dev_err(&sdev->udev->dev, "alloc stub_priv\n"); spin_unlock_irqrestore(&sdev->priv_lock, flags); usbip_event_add(ud, SDEV_EVENT_ERROR_MALLOC); return NULL; @@ -352,7 +347,7 @@ static int get_pipe(struct stub_device *sdev, int epnum, int dir) else ep = udev->ep_out[epnum & 0x7f]; if (!ep) { - dev_err(&sdev->interface->dev, "no such endpoint?, %d\n", + dev_err(&sdev->udev->dev, "no such endpoint?, %d\n", epnum); BUG(); } @@ -387,7 +382,7 @@ static int get_pipe(struct stub_device *sdev, int epnum, int dir) } /* NOT REACHED */ - dev_err(&sdev->interface->dev, "get pipe, epnum %d\n", epnum); + dev_err(&sdev->udev->dev, "get pipe, epnum %d\n", epnum); return 0; } @@ -466,7 +461,7 @@ static void stub_recv_cmd_submit(struct stub_device *sdev, priv->urb = usb_alloc_urb(0, GFP_KERNEL); if (!priv->urb) { - dev_err(&sdev->interface->dev, "malloc urb\n"); + dev_err(&udev->dev, "malloc urb\n"); usbip_event_add(ud, SDEV_EVENT_ERROR_MALLOC); return; } @@ -486,7 +481,7 @@ static void stub_recv_cmd_submit(struct stub_device *sdev, priv->urb->setup_packet = kmemdup(&pdu->u.cmd_submit.setup, 8, GFP_KERNEL); if (!priv->urb->setup_packet) { - dev_err(&sdev->interface->dev, "allocate setup_packet\n"); + dev_err(&udev->dev, "allocate setup_packet\n"); usbip_event_add(ud, SDEV_EVENT_ERROR_MALLOC); return; } @@ -517,7 +512,7 @@ static void stub_recv_cmd_submit(struct stub_device *sdev, usbip_dbg_stub_rx("submit urb ok, seqnum %u\n", pdu->base.seqnum); else { - dev_err(&sdev->interface->dev, "submit_urb error, %d\n", ret); + dev_err(&udev->dev, "submit_urb error, %d\n", ret); usbip_dump_header(pdu); usbip_dump_urb(priv->urb); diff --git a/drivers/usb/usbip/stub_tx.c b/drivers/usb/usbip/stub_tx.c index dbcabc9dbe0d..6b1e8c3f0e4b 100644 --- a/drivers/usb/usbip/stub_tx.c +++ b/drivers/usb/usbip/stub_tx.c @@ -97,7 +97,10 @@ void stub_complete(struct urb *urb) /* link a urb to the queue of tx. */ spin_lock_irqsave(&sdev->priv_lock, flags); - if (priv->unlinking) { + if (sdev->ud.tcp_socket == NULL) { + usbip_dbg_stub_tx("ignore urb for closed connection %p", urb); + /* It will be freed in stub_device_cleanup_urbs(). */ + } else if (priv->unlinking) { stub_enqueue_ret_unlink(sdev, priv->seqnum, urb->status); stub_free_priv_and_urb(priv); } else { @@ -229,7 +232,7 @@ static int stub_send_ret_submit(struct stub_device *sdev) } if (txsize != sizeof(pdu_header) + urb->actual_length) { - dev_err(&sdev->interface->dev, + dev_err(&sdev->udev->dev, "actual length of urb %d does not match iso packet sizes %zu\n", urb->actual_length, txsize-sizeof(pdu_header)); @@ -261,7 +264,7 @@ static int stub_send_ret_submit(struct stub_device *sdev) ret = kernel_sendmsg(sdev->ud.tcp_socket, &msg, iov, iovnum, txsize); if (ret != txsize) { - dev_err(&sdev->interface->dev, + dev_err(&sdev->udev->dev, "sendmsg failed!, retval %d for %zd\n", ret, txsize); kfree(iov); @@ -336,7 +339,7 @@ static int stub_send_ret_unlink(struct stub_device *sdev) ret = kernel_sendmsg(sdev->ud.tcp_socket, &msg, iov, 1, txsize); if (ret != txsize) { - dev_err(&sdev->interface->dev, + dev_err(&sdev->udev->dev, "sendmsg failed!, retval %d for %zd\n", ret, txsize); usbip_event_add(&sdev->ud, SDEV_EVENT_ERROR_TCP); diff --git a/drivers/usb/usbip/usbip_common.c b/drivers/usb/usbip/usbip_common.c index e40da7759a0e..8b232290be6b 100644 --- a/drivers/usb/usbip/usbip_common.c +++ b/drivers/usb/usbip/usbip_common.c @@ -1,5 +1,7 @@ /* * Copyright (C) 2003-2008 Takahiro Hirofuchi + * Copyright (C) 2015-2016 Samsung Electronics + * Krzysztof Opasiak <k.opasiak@samsung.com> * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -643,7 +645,7 @@ int usbip_recv_iso(struct usbip_device *ud, struct urb *urb) ret); kfree(buff); - if (ud->side == USBIP_STUB) + if (ud->side == USBIP_STUB || ud->side == USBIP_VUDC) usbip_event_add(ud, SDEV_EVENT_ERROR_TCP); else usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); @@ -665,7 +667,7 @@ int usbip_recv_iso(struct usbip_device *ud, struct urb *urb) "total length of iso packets %d not equal to actual length of buffer %d\n", total_length, urb->actual_length); - if (ud->side == USBIP_STUB) + if (ud->side == USBIP_STUB || ud->side == USBIP_VUDC) usbip_event_add(ud, SDEV_EVENT_ERROR_TCP); else usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); @@ -723,7 +725,7 @@ int usbip_recv_xbuff(struct usbip_device *ud, struct urb *urb) int ret; int size; - if (ud->side == USBIP_STUB) { + if (ud->side == USBIP_STUB || ud->side == USBIP_VUDC) { /* the direction of urb must be OUT. */ if (usb_pipein(urb->pipe)) return 0; @@ -755,7 +757,7 @@ int usbip_recv_xbuff(struct usbip_device *ud, struct urb *urb) ret = usbip_recv(ud->tcp_socket, urb->transfer_buffer, size); if (ret != size) { dev_err(&urb->dev->dev, "recv xbuf, %d\n", ret); - if (ud->side == USBIP_STUB) { + if (ud->side == USBIP_STUB || ud->side == USBIP_VUDC) { usbip_event_add(ud, SDEV_EVENT_ERROR_TCP); } else { usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); @@ -769,12 +771,19 @@ EXPORT_SYMBOL_GPL(usbip_recv_xbuff); static int __init usbip_core_init(void) { + int ret; + pr_info(DRIVER_DESC " v" USBIP_VERSION "\n"); + ret = usbip_init_eh(); + if (ret) + return ret; + return 0; } static void __exit usbip_core_exit(void) { + usbip_finish_eh(); return; } diff --git a/drivers/usb/usbip/usbip_common.h b/drivers/usb/usbip/usbip_common.h index 86b08475c254..c7508cbce3ce 100644 --- a/drivers/usb/usbip/usbip_common.h +++ b/drivers/usb/usbip/usbip_common.h @@ -1,5 +1,7 @@ /* * Copyright (C) 2003-2008 Takahiro Hirofuchi + * Copyright (C) 2015-2016 Samsung Electronics + * Krzysztof Opasiak <k.opasiak@samsung.com> * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -234,6 +236,7 @@ struct usbip_iso_packet_descriptor { enum usbip_side { USBIP_VHCI, USBIP_STUB, + USBIP_VUDC, }; /* event handler */ @@ -248,6 +251,13 @@ enum usbip_side { #define SDEV_EVENT_ERROR_SUBMIT (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) #define SDEV_EVENT_ERROR_MALLOC (USBIP_EH_SHUTDOWN | USBIP_EH_UNUSABLE) +#define VUDC_EVENT_REMOVED (USBIP_EH_SHUTDOWN | USBIP_EH_RESET | USBIP_EH_BYE) +#define VUDC_EVENT_DOWN (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) +#define VUDC_EVENT_ERROR_TCP (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) +/* catastrophic emulated usb error */ +#define VUDC_EVENT_ERROR_USB (USBIP_EH_SHUTDOWN | USBIP_EH_UNUSABLE) +#define VUDC_EVENT_ERROR_MALLOC (USBIP_EH_SHUTDOWN | USBIP_EH_UNUSABLE) + #define VDEV_EVENT_REMOVED (USBIP_EH_SHUTDOWN | USBIP_EH_BYE) #define VDEV_EVENT_DOWN (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) #define VDEV_EVENT_ERROR_TCP (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) @@ -267,7 +277,6 @@ struct usbip_device { struct task_struct *tcp_tx; unsigned long event; - struct task_struct *eh; wait_queue_head_t eh_waitq; struct eh_ops { @@ -313,10 +322,13 @@ void usbip_pad_iso(struct usbip_device *ud, struct urb *urb); int usbip_recv_xbuff(struct usbip_device *ud, struct urb *urb); /* usbip_event.c */ +int usbip_init_eh(void); +void usbip_finish_eh(void); int usbip_start_eh(struct usbip_device *ud); void usbip_stop_eh(struct usbip_device *ud); void usbip_event_add(struct usbip_device *ud, unsigned long event); int usbip_event_happened(struct usbip_device *ud); +int usbip_in_eh(struct task_struct *task); static inline int interface_to_busnum(struct usb_interface *interface) { diff --git a/drivers/usb/usbip/usbip_event.c b/drivers/usb/usbip/usbip_event.c index 2580a32bcdff..f1635662c299 100644 --- a/drivers/usb/usbip/usbip_event.c +++ b/drivers/usb/usbip/usbip_event.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2003-2008 Takahiro Hirofuchi + * Copyright (C) 2015 Nobuo Iwata * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,17 +20,68 @@ #include <linux/kthread.h> #include <linux/export.h> +#include <linux/slab.h> +#include <linux/workqueue.h> #include "usbip_common.h" -static int event_handler(struct usbip_device *ud) +struct usbip_event { + struct list_head node; + struct usbip_device *ud; +}; + +static DEFINE_SPINLOCK(event_lock); +static LIST_HEAD(event_list); + +static void set_event(struct usbip_device *ud, unsigned long event) { - usbip_dbg_eh("enter\n"); + unsigned long flags; - /* - * Events are handled by only this thread. - */ - while (usbip_event_happened(ud)) { + spin_lock_irqsave(&ud->lock, flags); + ud->event |= event; + spin_unlock_irqrestore(&ud->lock, flags); +} + +static void unset_event(struct usbip_device *ud, unsigned long event) +{ + unsigned long flags; + + spin_lock_irqsave(&ud->lock, flags); + ud->event &= ~event; + spin_unlock_irqrestore(&ud->lock, flags); +} + +static struct usbip_device *get_event(void) +{ + struct usbip_event *ue = NULL; + struct usbip_device *ud = NULL; + unsigned long flags; + + spin_lock_irqsave(&event_lock, flags); + if (!list_empty(&event_list)) { + ue = list_first_entry(&event_list, struct usbip_event, node); + list_del(&ue->node); + } + spin_unlock_irqrestore(&event_lock, flags); + + if (ue) { + ud = ue->ud; + kfree(ue); + } + return ud; +} + +static struct task_struct *worker_context; + +static void event_handler(struct work_struct *work) +{ + struct usbip_device *ud; + + if (worker_context == NULL) { + worker_context = current; + } + + while ((ud = get_event()) != NULL) { usbip_dbg_eh("pending event %lx\n", ud->event); /* @@ -38,79 +90,102 @@ static int event_handler(struct usbip_device *ud) */ if (ud->event & USBIP_EH_SHUTDOWN) { ud->eh_ops.shutdown(ud); - ud->event &= ~USBIP_EH_SHUTDOWN; + unset_event(ud, USBIP_EH_SHUTDOWN); } /* Reset the device. */ if (ud->event & USBIP_EH_RESET) { ud->eh_ops.reset(ud); - ud->event &= ~USBIP_EH_RESET; + unset_event(ud, USBIP_EH_RESET); } /* Mark the device as unusable. */ if (ud->event & USBIP_EH_UNUSABLE) { ud->eh_ops.unusable(ud); - ud->event &= ~USBIP_EH_UNUSABLE; + unset_event(ud, USBIP_EH_UNUSABLE); } /* Stop the error handler. */ if (ud->event & USBIP_EH_BYE) - return -1; + usbip_dbg_eh("removed %p\n", ud); + + wake_up(&ud->eh_waitq); } +} +int usbip_start_eh(struct usbip_device *ud) +{ + init_waitqueue_head(&ud->eh_waitq); + ud->event = 0; return 0; } +EXPORT_SYMBOL_GPL(usbip_start_eh); -static int event_handler_loop(void *data) +void usbip_stop_eh(struct usbip_device *ud) { - struct usbip_device *ud = data; + unsigned long pending = ud->event & ~USBIP_EH_BYE; - while (!kthread_should_stop()) { - wait_event_interruptible(ud->eh_waitq, - usbip_event_happened(ud) || - kthread_should_stop()); - usbip_dbg_eh("wakeup\n"); + if (!(ud->event & USBIP_EH_BYE)) + usbip_dbg_eh("usbip_eh stopping but not removed\n"); - if (event_handler(ud) < 0) - break; - } + if (pending) + usbip_dbg_eh("usbip_eh waiting completion %lx\n", pending); - return 0; + wait_event_interruptible(ud->eh_waitq, !(ud->event & ~USBIP_EH_BYE)); + usbip_dbg_eh("usbip_eh has stopped\n"); } +EXPORT_SYMBOL_GPL(usbip_stop_eh); -int usbip_start_eh(struct usbip_device *ud) -{ - init_waitqueue_head(&ud->eh_waitq); - ud->event = 0; +#define WORK_QUEUE_NAME "usbip_event" - ud->eh = kthread_run(event_handler_loop, ud, "usbip_eh"); - if (IS_ERR(ud->eh)) { - pr_warn("Unable to start control thread\n"); - return PTR_ERR(ud->eh); - } +static struct workqueue_struct *usbip_queue; +static DECLARE_WORK(usbip_work, event_handler); +int usbip_init_eh(void) +{ + usbip_queue = create_singlethread_workqueue(WORK_QUEUE_NAME); + if (usbip_queue == NULL) { + pr_err("failed to create usbip_event\n"); + return -ENOMEM; + } return 0; } -EXPORT_SYMBOL_GPL(usbip_start_eh); -void usbip_stop_eh(struct usbip_device *ud) +void usbip_finish_eh(void) { - if (ud->eh == current) - return; /* do not wait for myself */ - - kthread_stop(ud->eh); - usbip_dbg_eh("usbip_eh has finished\n"); + flush_workqueue(usbip_queue); + destroy_workqueue(usbip_queue); + usbip_queue = NULL; } -EXPORT_SYMBOL_GPL(usbip_stop_eh); void usbip_event_add(struct usbip_device *ud, unsigned long event) { + struct usbip_event *ue; unsigned long flags; - spin_lock_irqsave(&ud->lock, flags); - ud->event |= event; - wake_up(&ud->eh_waitq); - spin_unlock_irqrestore(&ud->lock, flags); + if (ud->event & USBIP_EH_BYE) + return; + + set_event(ud, event); + + spin_lock_irqsave(&event_lock, flags); + + list_for_each_entry_reverse(ue, &event_list, node) { + if (ue->ud == ud) + goto out; + } + + ue = kmalloc(sizeof(struct usbip_event), GFP_ATOMIC); + if (ue == NULL) + goto out; + + ue->ud = ud; + + list_add_tail(&ue->node, &event_list); + queue_work(usbip_queue, &usbip_work); + +out: + spin_unlock_irqrestore(&event_lock, flags); } EXPORT_SYMBOL_GPL(usbip_event_add); @@ -127,3 +202,12 @@ int usbip_event_happened(struct usbip_device *ud) return happened; } EXPORT_SYMBOL_GPL(usbip_event_happened); + +int usbip_in_eh(struct task_struct *task) +{ + if (task == worker_context) + return 1; + + return 0; +} +EXPORT_SYMBOL_GPL(usbip_in_eh); diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index fca51105974e..2e0450bec1b1 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -941,7 +941,7 @@ static void vhci_stop(struct usb_hcd *hcd) static int vhci_get_frame_number(struct usb_hcd *hcd) { - pr_err("Not yet implemented\n"); + dev_err_ratelimited(&hcd->self.root_hub->dev, "Not yet implemented\n"); return 0; } diff --git a/drivers/usb/usbip/vudc.h b/drivers/usb/usbip/vudc.h new file mode 100644 index 000000000000..25e01b09c4c3 --- /dev/null +++ b/drivers/usb/usbip/vudc.h @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2015 Karol Kosik <karo9@interia.eu> + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski <i.kotrasinsk@samsung.com> + * Krzysztof Opasiak <k.opasiak@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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 __USBIP_VUDC_H +#define __USBIP_VUDC_H + +#include <linux/platform_device.h> +#include <linux/usb.h> +#include <linux/usb/gadget.h> +#include <linux/usb/ch9.h> +#include <linux/list.h> +#include <linux/timer.h> +#include <linux/time.h> +#include <linux/sysfs.h> + +#include "usbip_common.h" + +#define GADGET_NAME "usbip-vudc" + +struct vep { + struct usb_ep ep; + unsigned type:2; /* type, as USB_ENDPOINT_XFER_* */ + char name[8]; /* space for ep name */ + + const struct usb_endpoint_descriptor *desc; + struct usb_gadget *gadget; + struct list_head req_queue; /* Request queue */ + unsigned halted:1; + unsigned wedged:1; + unsigned already_seen:1; + unsigned setup_stage:1; +}; + +struct vrequest { + struct usb_request req; + struct vudc *udc; + struct list_head req_entry; /* Request queue */ +}; + +struct urbp { + struct urb *urb; + struct vep *ep; + struct list_head urb_entry; /* urb queue */ + unsigned long seqnum; + unsigned type:2; /* for tx, since ep type can change after */ + unsigned new:1; +}; + +struct v_unlink { + unsigned long seqnum; + __u32 status; +}; + +enum tx_type { + TX_UNLINK, + TX_SUBMIT, +}; + +struct tx_item { + struct list_head tx_entry; + enum tx_type type; + union { + struct urbp *s; + struct v_unlink *u; + }; +}; + +enum tr_state { + VUDC_TR_RUNNING, + VUDC_TR_IDLE, + VUDC_TR_STOPPED, +}; + +struct transfer_timer { + struct timer_list timer; + enum tr_state state; + unsigned long frame_start; + int frame_limit; +}; + +struct vudc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct platform_device *pdev; + + struct usb_device_descriptor dev_desc; + + struct usbip_device ud; + struct transfer_timer tr_timer; + struct timeval start_time; + + struct list_head urb_queue; + + spinlock_t lock_tx; + struct list_head tx_queue; + wait_queue_head_t tx_waitq; + + spinlock_t lock; + struct vep *ep; + int address; + u16 devstatus; + + unsigned pullup:1; + unsigned connected:1; + unsigned desc_cached:1; +}; + +struct vudc_device { + struct platform_device *pdev; + struct list_head dev_entry; +}; + +extern const struct attribute_group vudc_attr_group; + +/* visible everywhere */ + +static inline struct vep *to_vep(struct usb_ep *_ep) +{ + return container_of(_ep, struct vep, ep); +} + +static inline struct vrequest *to_vrequest( + struct usb_request *_req) +{ + return container_of(_req, struct vrequest, req); +} + +static inline struct vudc *usb_gadget_to_vudc( + struct usb_gadget *_gadget) +{ + return container_of(_gadget, struct vudc, gadget); +} + +static inline struct vudc *ep_to_vudc(struct vep *ep) +{ + return container_of(ep->gadget, struct vudc, gadget); +} + +/* vudc_sysfs.c */ + +int get_gadget_descs(struct vudc *udc); + +/* vudc_tx.c */ + +int v_tx_loop(void *data); +void v_enqueue_ret_unlink(struct vudc *udc, __u32 seqnum, __u32 status); +void v_enqueue_ret_submit(struct vudc *udc, struct urbp *urb_p); + +/* vudc_rx.c */ + +int v_rx_loop(void *data); + +/* vudc_transfer.c */ + +void v_init_timer(struct vudc *udc); +void v_start_timer(struct vudc *udc); +void v_kick_timer(struct vudc *udc, unsigned long time); +void v_stop_timer(struct vudc *udc); + +/* vudc_dev.c */ + +struct urbp *alloc_urbp(void); +void free_urbp_and_urb(struct urbp *urb_p); + +struct vep *vudc_find_endpoint(struct vudc *udc, u8 address); + +struct vudc_device *alloc_vudc_device(int devid); +void put_vudc_device(struct vudc_device *udc_dev); + +int vudc_probe(struct platform_device *pdev); +int vudc_remove(struct platform_device *pdev); + +#endif /* __USBIP_VUDC_H */ diff --git a/drivers/usb/usbip/vudc_dev.c b/drivers/usb/usbip/vudc_dev.c new file mode 100644 index 000000000000..8994a13819ab --- /dev/null +++ b/drivers/usb/usbip/vudc_dev.c @@ -0,0 +1,661 @@ +/* + * Copyright (C) 2015 Karol Kosik <karo9@interia.eu> + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski <i.kotrasinsk@samsung.com> + * Krzysztof Opasiak <k.opasiak@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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/device.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/platform_device.h> +#include <linux/usb.h> +#include <linux/usb/gadget.h> +#include <linux/usb/hcd.h> +#include <linux/kthread.h> +#include <linux/file.h> +#include <linux/byteorder/generic.h> + +#include "usbip_common.h" +#include "vudc.h" + +#define VIRTUAL_ENDPOINTS (1 /* ep0 */ + 15 /* in eps */ + 15 /* out eps */) + +/* urb-related structures alloc / free */ + + +static void free_urb(struct urb *urb) +{ + if (!urb) + return; + + kfree(urb->setup_packet); + urb->setup_packet = NULL; + + kfree(urb->transfer_buffer); + urb->transfer_buffer = NULL; + + usb_free_urb(urb); +} + +struct urbp *alloc_urbp(void) +{ + struct urbp *urb_p; + + urb_p = kzalloc(sizeof(*urb_p), GFP_KERNEL); + if (!urb_p) + return urb_p; + + urb_p->urb = NULL; + urb_p->ep = NULL; + INIT_LIST_HEAD(&urb_p->urb_entry); + return urb_p; +} + +static void free_urbp(struct urbp *urb_p) +{ + kfree(urb_p); +} + +void free_urbp_and_urb(struct urbp *urb_p) +{ + if (!urb_p) + return; + free_urb(urb_p->urb); + free_urbp(urb_p); +} + + +/* utilities ; almost verbatim from dummy_hcd.c */ + +/* called with spinlock held */ +static void nuke(struct vudc *udc, struct vep *ep) +{ + struct vrequest *req; + + while (!list_empty(&ep->req_queue)) { + req = list_first_entry(&ep->req_queue, struct vrequest, + req_entry); + list_del_init(&req->req_entry); + req->req.status = -ESHUTDOWN; + + spin_unlock(&udc->lock); + usb_gadget_giveback_request(&ep->ep, &req->req); + spin_lock(&udc->lock); + } +} + +/* caller must hold lock */ +static void stop_activity(struct vudc *udc) +{ + int i; + struct urbp *urb_p, *tmp; + + udc->address = 0; + + for (i = 0; i < VIRTUAL_ENDPOINTS; i++) + nuke(udc, &udc->ep[i]); + + list_for_each_entry_safe(urb_p, tmp, &udc->urb_queue, urb_entry) { + list_del(&urb_p->urb_entry); + free_urbp_and_urb(urb_p); + } +} + +struct vep *vudc_find_endpoint(struct vudc *udc, u8 address) +{ + int i; + + if ((address & ~USB_DIR_IN) == 0) + return &udc->ep[0]; + + for (i = 1; i < VIRTUAL_ENDPOINTS; i++) { + struct vep *ep = &udc->ep[i]; + + if (!ep->desc) + continue; + if (ep->desc->bEndpointAddress == address) + return ep; + } + return NULL; +} + +/* gadget ops */ + +/* FIXME - this will probably misbehave when suspend/resume is added */ +static int vgadget_get_frame(struct usb_gadget *_gadget) +{ + struct timeval now; + struct vudc *udc = usb_gadget_to_vudc(_gadget); + + do_gettimeofday(&now); + return ((now.tv_sec - udc->start_time.tv_sec) * 1000 + + (now.tv_usec - udc->start_time.tv_usec) / 1000) + % 0x7FF; +} + +static int vgadget_set_selfpowered(struct usb_gadget *_gadget, int value) +{ + struct vudc *udc = usb_gadget_to_vudc(_gadget); + + if (value) + udc->devstatus |= (1 << USB_DEVICE_SELF_POWERED); + else + udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); + return 0; +} + +static int vgadget_pullup(struct usb_gadget *_gadget, int value) +{ + struct vudc *udc = usb_gadget_to_vudc(_gadget); + unsigned long flags; + int ret; + + + spin_lock_irqsave(&udc->lock, flags); + value = !!value; + if (value == udc->pullup) + goto unlock; + + udc->pullup = value; + if (value) { + udc->gadget.speed = min_t(u8, USB_SPEED_HIGH, + udc->driver->max_speed); + udc->ep[0].ep.maxpacket = 64; + /* + * This is the first place where we can ask our + * gadget driver for descriptors. + */ + ret = get_gadget_descs(udc); + if (ret) { + dev_err(&udc->gadget.dev, "Unable go get desc: %d", ret); + goto unlock; + } + + spin_unlock_irqrestore(&udc->lock, flags); + usbip_start_eh(&udc->ud); + } else { + /* Invalidate descriptors */ + udc->desc_cached = 0; + + spin_unlock_irqrestore(&udc->lock, flags); + usbip_event_add(&udc->ud, VUDC_EVENT_REMOVED); + usbip_stop_eh(&udc->ud); /* Wait for eh completion */ + } + + return 0; + +unlock: + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static int vgadget_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct vudc *udc = usb_gadget_to_vudc(g); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + udc->driver = driver; + udc->pullup = udc->connected = udc->desc_cached = 0; + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int vgadget_udc_stop(struct usb_gadget *g) +{ + struct vudc *udc = usb_gadget_to_vudc(g); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + udc->driver = NULL; + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static const struct usb_gadget_ops vgadget_ops = { + .get_frame = vgadget_get_frame, + .set_selfpowered = vgadget_set_selfpowered, + .pullup = vgadget_pullup, + .udc_start = vgadget_udc_start, + .udc_stop = vgadget_udc_stop, +}; + + +/* endpoint ops */ + +static int vep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct vep *ep; + struct vudc *udc; + unsigned maxp; + unsigned long flags; + + ep = to_vep(_ep); + udc = ep_to_vudc(ep); + + if (!_ep || !desc || ep->desc || _ep->caps.type_control + || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + + if (!udc->driver) + return -ESHUTDOWN; + + spin_lock_irqsave(&udc->lock, flags); + + maxp = usb_endpoint_maxp(desc) & 0x7ff; + _ep->maxpacket = maxp; + ep->desc = desc; + ep->type = usb_endpoint_type(desc); + ep->halted = ep->wedged = 0; + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int vep_disable(struct usb_ep *_ep) +{ + struct vep *ep; + struct vudc *udc; + unsigned long flags; + + ep = to_vep(_ep); + udc = ep_to_vudc(ep); + if (!_ep || !ep->desc || _ep->caps.type_control) + return -EINVAL; + + spin_lock_irqsave(&udc->lock, flags); + ep->desc = NULL; + nuke(udc, ep); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static struct usb_request *vep_alloc_request(struct usb_ep *_ep, + gfp_t mem_flags) +{ + struct vep *ep; + struct vrequest *req; + + if (!_ep) + return NULL; + ep = to_vep(_ep); + + req = kzalloc(sizeof(*req), mem_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->req_entry); + + return &req->req; +} + +static void vep_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct vrequest *req; + + if (WARN_ON(!_ep || !_req)) + return; + + req = to_vrequest(_req); + kfree(req); +} + +static int vep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t mem_flags) +{ + struct vep *ep; + struct vrequest *req; + struct vudc *udc; + unsigned long flags; + + if (!_ep || !_req) + return -EINVAL; + + ep = to_vep(_ep); + req = to_vrequest(_req); + udc = ep_to_vudc(ep); + + spin_lock_irqsave(&udc->lock, flags); + _req->actual = 0; + _req->status = -EINPROGRESS; + + list_add_tail(&req->req_entry, &ep->req_queue); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int vep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct vep *ep; + struct vrequest *req; + struct vudc *udc; + struct vrequest *lst; + unsigned long flags; + int ret = -EINVAL; + + if (!_ep || !_req) + return ret; + + ep = to_vep(_ep); + req = to_vrequest(_req); + udc = req->udc; + + if (!udc->driver) + return -ESHUTDOWN; + + spin_lock_irqsave(&udc->lock, flags); + list_for_each_entry(lst, &ep->req_queue, req_entry) { + if (&lst->req == _req) { + list_del_init(&lst->req_entry); + _req->status = -ECONNRESET; + ret = 0; + break; + } + } + spin_unlock_irqrestore(&udc->lock, flags); + + if (ret == 0) + usb_gadget_giveback_request(_ep, _req); + + return ret; +} + +static int +vep_set_halt_and_wedge(struct usb_ep *_ep, int value, int wedged) +{ + struct vep *ep; + struct vudc *udc; + unsigned long flags; + int ret = 0; + + ep = to_vep(_ep); + if (!_ep) + return -EINVAL; + + udc = ep_to_vudc(ep); + if (!udc->driver) + return -ESHUTDOWN; + + spin_lock_irqsave(&udc->lock, flags); + if (!value) + ep->halted = ep->wedged = 0; + else if (ep->desc && (ep->desc->bEndpointAddress & USB_DIR_IN) && + !list_empty(&ep->req_queue)) + ret = -EAGAIN; + else { + ep->halted = 1; + if (wedged) + ep->wedged = 1; + } + + spin_unlock_irqrestore(&udc->lock, flags); + return ret; +} + +static int +vep_set_halt(struct usb_ep *_ep, int value) +{ + return vep_set_halt_and_wedge(_ep, value, 0); +} + +static int vep_set_wedge(struct usb_ep *_ep) +{ + return vep_set_halt_and_wedge(_ep, 1, 1); +} + +static const struct usb_ep_ops vep_ops = { + .enable = vep_enable, + .disable = vep_disable, + + .alloc_request = vep_alloc_request, + .free_request = vep_free_request, + + .queue = vep_queue, + .dequeue = vep_dequeue, + + .set_halt = vep_set_halt, + .set_wedge = vep_set_wedge, +}; + + +/* shutdown / reset / error handlers */ + +static void vudc_shutdown(struct usbip_device *ud) +{ + struct vudc *udc = container_of(ud, struct vudc, ud); + int call_disconnect = 0; + unsigned long flags; + + dev_dbg(&udc->pdev->dev, "device shutdown"); + if (ud->tcp_socket) + kernel_sock_shutdown(ud->tcp_socket, SHUT_RDWR); + + if (ud->tcp_tx) { + kthread_stop_put(ud->tcp_rx); + ud->tcp_rx = NULL; + } + if (ud->tcp_tx) { + kthread_stop_put(ud->tcp_tx); + ud->tcp_tx = NULL; + } + + if (ud->tcp_socket) { + sockfd_put(ud->tcp_socket); + ud->tcp_socket = NULL; + } + + spin_lock_irqsave(&udc->lock, flags); + stop_activity(udc); + if (udc->connected && udc->driver->disconnect) + call_disconnect = 1; + udc->connected = 0; + spin_unlock_irqrestore(&udc->lock, flags); + if (call_disconnect) + udc->driver->disconnect(&udc->gadget); +} + +static void vudc_device_reset(struct usbip_device *ud) +{ + struct vudc *udc = container_of(ud, struct vudc, ud); + unsigned long flags; + + dev_dbg(&udc->pdev->dev, "device reset"); + spin_lock_irqsave(&udc->lock, flags); + stop_activity(udc); + spin_unlock_irqrestore(&udc->lock, flags); + if (udc->driver) + usb_gadget_udc_reset(&udc->gadget, udc->driver); + spin_lock_irqsave(&ud->lock, flags); + ud->status = SDEV_ST_AVAILABLE; + spin_unlock_irqrestore(&ud->lock, flags); +} + +static void vudc_device_unusable(struct usbip_device *ud) +{ + unsigned long flags; + + spin_lock_irqsave(&ud->lock, flags); + ud->status = SDEV_ST_ERROR; + spin_unlock_irqrestore(&ud->lock, flags); +} + +/* device setup / cleanup */ + +struct vudc_device *alloc_vudc_device(int devid) +{ + struct vudc_device *udc_dev = NULL; + + udc_dev = kzalloc(sizeof(*udc_dev), GFP_KERNEL); + if (!udc_dev) + goto out; + + INIT_LIST_HEAD(&udc_dev->dev_entry); + + udc_dev->pdev = platform_device_alloc(GADGET_NAME, devid); + if (!udc_dev->pdev) { + kfree(udc_dev); + udc_dev = NULL; + } + +out: + return udc_dev; +} + +void put_vudc_device(struct vudc_device *udc_dev) +{ + platform_device_put(udc_dev->pdev); + kfree(udc_dev); +} + +static int init_vudc_hw(struct vudc *udc) +{ + int i; + struct usbip_device *ud = &udc->ud; + struct vep *ep; + + udc->ep = kcalloc(VIRTUAL_ENDPOINTS, sizeof(*udc->ep), GFP_KERNEL); + if (!udc->ep) + goto nomem_ep; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + + /* create ep0 and 15 in, 15 out general purpose eps */ + for (i = 0; i < VIRTUAL_ENDPOINTS; ++i) { + int is_out = i % 2; + int num = (i + 1) / 2; + + ep = &udc->ep[i]; + + sprintf(ep->name, "ep%d%s", num, + i ? (is_out ? "out" : "in") : ""); + ep->ep.name = ep->name; + if (i == 0) { + ep->ep.caps.type_control = true; + ep->ep.caps.dir_out = true; + ep->ep.caps.dir_in = true; + } else { + ep->ep.caps.type_iso = true; + ep->ep.caps.type_int = true; + ep->ep.caps.type_bulk = true; + } + + if (is_out) + ep->ep.caps.dir_out = true; + else + ep->ep.caps.dir_in = true; + + ep->ep.ops = &vep_ops; + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + ep->halted = ep->wedged = ep->already_seen = + ep->setup_stage = 0; + usb_ep_set_maxpacket_limit(&ep->ep, ~0); + ep->ep.max_streams = 16; + ep->gadget = &udc->gadget; + ep->desc = NULL; + INIT_LIST_HEAD(&ep->req_queue); + } + + spin_lock_init(&udc->lock); + spin_lock_init(&udc->lock_tx); + INIT_LIST_HEAD(&udc->urb_queue); + INIT_LIST_HEAD(&udc->tx_queue); + init_waitqueue_head(&udc->tx_waitq); + + spin_lock_init(&ud->lock); + ud->status = SDEV_ST_AVAILABLE; + ud->side = USBIP_VUDC; + + ud->eh_ops.shutdown = vudc_shutdown; + ud->eh_ops.reset = vudc_device_reset; + ud->eh_ops.unusable = vudc_device_unusable; + + udc->gadget.ep0 = &udc->ep[0].ep; + list_del_init(&udc->ep[0].ep.ep_list); + + v_init_timer(udc); + return 0; + +nomem_ep: + return -ENOMEM; +} + +static void cleanup_vudc_hw(struct vudc *udc) +{ + kfree(udc->ep); +} + +/* platform driver ops */ + +int vudc_probe(struct platform_device *pdev) +{ + struct vudc *udc; + int ret = -ENOMEM; + + udc = kzalloc(sizeof(*udc), GFP_KERNEL); + if (!udc) + goto out; + + udc->gadget.name = GADGET_NAME; + udc->gadget.ops = &vgadget_ops; + udc->gadget.max_speed = USB_SPEED_HIGH; + udc->gadget.dev.parent = &pdev->dev; + udc->pdev = pdev; + + ret = init_vudc_hw(udc); + if (ret) + goto err_init_vudc_hw; + + ret = usb_add_gadget_udc(&pdev->dev, &udc->gadget); + if (ret < 0) + goto err_add_udc; + + ret = sysfs_create_group(&pdev->dev.kobj, &vudc_attr_group); + if (ret) { + dev_err(&udc->pdev->dev, "create sysfs files\n"); + goto err_sysfs; + } + + platform_set_drvdata(pdev, udc); + + return ret; + +err_sysfs: + usb_del_gadget_udc(&udc->gadget); +err_add_udc: + cleanup_vudc_hw(udc); +err_init_vudc_hw: + kfree(udc); +out: + return ret; +} + +int vudc_remove(struct platform_device *pdev) +{ + struct vudc *udc = platform_get_drvdata(pdev); + + sysfs_remove_group(&pdev->dev.kobj, &vudc_attr_group); + usb_del_gadget_udc(&udc->gadget); + cleanup_vudc_hw(udc); + kfree(udc); + return 0; +} diff --git a/drivers/usb/usbip/vudc_main.c b/drivers/usb/usbip/vudc_main.c new file mode 100644 index 000000000000..9e655714e389 --- /dev/null +++ b/drivers/usb/usbip/vudc_main.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2015 Karol Kosik <karo9@interia.eu> + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski <i.kotrasinsk@samsung.com> + * Krzysztof Opasiak <k.opasiak@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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/device.h> +#include <linux/list.h> +#include <linux/module.h> + +#include "vudc.h" + +static unsigned int vudc_number = 1; + +module_param_named(num, vudc_number, uint, S_IRUGO); +MODULE_PARM_DESC(num, "number of emulated controllers"); + +static struct platform_driver vudc_driver = { + .probe = vudc_probe, + .remove = vudc_remove, + .driver = { + .name = GADGET_NAME, + }, +}; + +static struct list_head vudc_devices = LIST_HEAD_INIT(vudc_devices); + +static int __init init(void) +{ + int retval = -ENOMEM; + int i; + struct vudc_device *udc_dev = NULL, *udc_dev2 = NULL; + + if (usb_disabled()) + return -ENODEV; + + if (vudc_number < 1) { + pr_err("Number of emulated UDC must be no less than 1"); + return -EINVAL; + } + + retval = platform_driver_register(&vudc_driver); + if (retval < 0) + goto out; + + for (i = 0; i < vudc_number; i++) { + udc_dev = alloc_vudc_device(i); + if (!udc_dev) { + retval = -ENOMEM; + goto cleanup; + } + + retval = platform_device_add(udc_dev->pdev); + if (retval < 0) { + put_vudc_device(udc_dev); + goto cleanup; + } + + list_add_tail(&udc_dev->dev_entry, &vudc_devices); + if (!platform_get_drvdata(udc_dev->pdev)) { + /* + * The udc was added successfully but its probe + * function failed for some reason. + */ + retval = -EINVAL; + goto cleanup; + } + } + goto out; + +cleanup: + list_for_each_entry_safe(udc_dev, udc_dev2, &vudc_devices, dev_entry) { + list_del(&udc_dev->dev_entry); + platform_device_del(udc_dev->pdev); + put_vudc_device(udc_dev); + } + + platform_driver_unregister(&vudc_driver); +out: + return retval; +} +module_init(init); + +static void __exit cleanup(void) +{ + struct vudc_device *udc_dev = NULL, *udc_dev2 = NULL; + + list_for_each_entry_safe(udc_dev, udc_dev2, &vudc_devices, dev_entry) { + list_del(&udc_dev->dev_entry); + platform_device_unregister(udc_dev->pdev); + put_vudc_device(udc_dev); + } + platform_driver_unregister(&vudc_driver); +} +module_exit(cleanup); + +MODULE_DESCRIPTION("USB over IP Device Controller"); +MODULE_AUTHOR("Krzysztof Opasiak, Karol Kosik, Igor Kotrasinski"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/usbip/vudc_rx.c b/drivers/usb/usbip/vudc_rx.c new file mode 100644 index 000000000000..344bd9473475 --- /dev/null +++ b/drivers/usb/usbip/vudc_rx.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2015 Karol Kosik <karo9@interia.eu> + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski <i.kotrasinsk@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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 <net/sock.h> +#include <linux/list.h> +#include <linux/kthread.h> + +#include "usbip_common.h" +#include "vudc.h" + +static int alloc_urb_from_cmd(struct urb **urbp, + struct usbip_header *pdu, u8 type) +{ + struct urb *urb; + + if (type == USB_ENDPOINT_XFER_ISOC) + urb = usb_alloc_urb(pdu->u.cmd_submit.number_of_packets, + GFP_KERNEL); + else + urb = usb_alloc_urb(0, GFP_KERNEL); + + if (!urb) + goto err; + + usbip_pack_pdu(pdu, urb, USBIP_CMD_SUBMIT, 0); + + if (urb->transfer_buffer_length > 0) { + urb->transfer_buffer = kzalloc(urb->transfer_buffer_length, + GFP_KERNEL); + if (!urb->transfer_buffer) + goto free_urb; + } + + urb->setup_packet = kmemdup(&pdu->u.cmd_submit.setup, 8, + GFP_KERNEL); + if (!urb->setup_packet) + goto free_buffer; + + /* + * FIXME - we only setup pipe enough for usbip functions + * to behave nicely + */ + urb->pipe |= pdu->base.direction == USBIP_DIR_IN ? + USB_DIR_IN : USB_DIR_OUT; + + *urbp = urb; + return 0; + +free_buffer: + kfree(urb->transfer_buffer); + urb->transfer_buffer = NULL; +free_urb: + usb_free_urb(urb); +err: + return -ENOMEM; +} + +static int v_recv_cmd_unlink(struct vudc *udc, + struct usbip_header *pdu) +{ + unsigned long flags; + struct urbp *urb_p; + + spin_lock_irqsave(&udc->lock, flags); + list_for_each_entry(urb_p, &udc->urb_queue, urb_entry) { + if (urb_p->seqnum != pdu->u.cmd_unlink.seqnum) + continue; + urb_p->urb->unlinked = -ECONNRESET; + urb_p->seqnum = pdu->base.seqnum; + v_kick_timer(udc, jiffies); + spin_unlock_irqrestore(&udc->lock, flags); + return 0; + } + /* Not found, completed / not queued */ + spin_lock(&udc->lock_tx); + v_enqueue_ret_unlink(udc, pdu->base.seqnum, 0); + wake_up(&udc->tx_waitq); + spin_unlock(&udc->lock_tx); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int v_recv_cmd_submit(struct vudc *udc, + struct usbip_header *pdu) +{ + int ret = 0; + struct urbp *urb_p; + u8 address; + unsigned long flags; + + urb_p = alloc_urbp(); + if (!urb_p) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_MALLOC); + return -ENOMEM; + } + + /* base.ep is pipeendpoint(pipe) */ + address = pdu->base.ep; + if (pdu->base.direction == USBIP_DIR_IN) + address |= USB_DIR_IN; + + spin_lock_irq(&udc->lock); + urb_p->ep = vudc_find_endpoint(udc, address); + if (!urb_p->ep) { + /* we don't know the type, there may be isoc data! */ + dev_err(&udc->pdev->dev, "request to nonexistent endpoint"); + spin_unlock_irq(&udc->lock); + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_TCP); + ret = -EPIPE; + goto free_urbp; + } + urb_p->type = urb_p->ep->type; + spin_unlock_irq(&udc->lock); + + urb_p->new = 1; + urb_p->seqnum = pdu->base.seqnum; + + ret = alloc_urb_from_cmd(&urb_p->urb, pdu, urb_p->ep->type); + if (ret) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_MALLOC); + ret = -ENOMEM; + goto free_urbp; + } + + urb_p->urb->status = -EINPROGRESS; + + /* FIXME: more pipe setup to please usbip_common */ + urb_p->urb->pipe &= ~(11 << 30); + switch (urb_p->ep->type) { + case USB_ENDPOINT_XFER_BULK: + urb_p->urb->pipe |= (PIPE_BULK << 30); + break; + case USB_ENDPOINT_XFER_INT: + urb_p->urb->pipe |= (PIPE_INTERRUPT << 30); + break; + case USB_ENDPOINT_XFER_CONTROL: + urb_p->urb->pipe |= (PIPE_CONTROL << 30); + break; + case USB_ENDPOINT_XFER_ISOC: + urb_p->urb->pipe |= (PIPE_ISOCHRONOUS << 30); + break; + } + ret = usbip_recv_xbuff(&udc->ud, urb_p->urb); + if (ret < 0) + goto free_urbp; + + ret = usbip_recv_iso(&udc->ud, urb_p->urb); + if (ret < 0) + goto free_urbp; + + spin_lock_irqsave(&udc->lock, flags); + v_kick_timer(udc, jiffies); + list_add_tail(&urb_p->urb_entry, &udc->urb_queue); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; + +free_urbp: + free_urbp_and_urb(urb_p); + return ret; +} + +static int v_rx_pdu(struct usbip_device *ud) +{ + int ret; + struct usbip_header pdu; + struct vudc *udc = container_of(ud, struct vudc, ud); + + memset(&pdu, 0, sizeof(pdu)); + ret = usbip_recv(ud->tcp_socket, &pdu, sizeof(pdu)); + if (ret != sizeof(pdu)) { + usbip_event_add(ud, VUDC_EVENT_ERROR_TCP); + if (ret >= 0) + return -EPIPE; + return ret; + } + usbip_header_correct_endian(&pdu, 0); + + spin_lock_irq(&ud->lock); + ret = (ud->status == SDEV_ST_USED); + spin_unlock_irq(&ud->lock); + if (!ret) { + usbip_event_add(ud, VUDC_EVENT_ERROR_TCP); + return -EBUSY; + } + + switch (pdu.base.command) { + case USBIP_CMD_UNLINK: + ret = v_recv_cmd_unlink(udc, &pdu); + break; + case USBIP_CMD_SUBMIT: + ret = v_recv_cmd_submit(udc, &pdu); + break; + default: + ret = -EPIPE; + pr_err("rx: unknown command"); + break; + } + return ret; +} + +int v_rx_loop(void *data) +{ + struct usbip_device *ud = data; + int ret = 0; + + while (!kthread_should_stop()) { + if (usbip_event_happened(ud)) + break; + ret = v_rx_pdu(ud); + if (ret < 0) { + pr_warn("v_rx exit with error %d", ret); + break; + } + } + return ret; +} diff --git a/drivers/usb/usbip/vudc_sysfs.c b/drivers/usb/usbip/vudc_sysfs.c new file mode 100644 index 000000000000..99397fa1e3f0 --- /dev/null +++ b/drivers/usb/usbip/vudc_sysfs.c @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2015 Karol Kosik <karo9@interia.eu> + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski <i.kotrasinsk@samsung.com> + * Krzysztof Opasiak <k.opasiak@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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/device.h> +#include <linux/list.h> +#include <linux/usb/gadget.h> +#include <linux/usb/ch9.h> +#include <linux/sysfs.h> +#include <linux/kthread.h> +#include <linux/byteorder/generic.h> + +#include "usbip_common.h" +#include "vudc.h" + +#include <net/sock.h> + +/* called with udc->lock held */ +int get_gadget_descs(struct vudc *udc) +{ + struct vrequest *usb_req; + struct vep *ep0 = to_vep(udc->gadget.ep0); + struct usb_device_descriptor *ddesc = &udc->dev_desc; + struct usb_ctrlrequest req; + int ret; + + if (!udc || !udc->driver || !udc->pullup) + return -EINVAL; + + req.bRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE; + req.bRequest = USB_REQ_GET_DESCRIPTOR; + req.wValue = cpu_to_le16(USB_DT_DEVICE << 8); + req.wIndex = cpu_to_le16(0); + req.wLength = cpu_to_le16(sizeof(*ddesc)); + + spin_unlock(&udc->lock); + ret = udc->driver->setup(&(udc->gadget), &req); + spin_lock(&udc->lock); + if (ret < 0) + goto out; + + /* assuming request queue is empty; request is now on top */ + usb_req = list_last_entry(&ep0->req_queue, struct vrequest, req_entry); + list_del(&usb_req->req_entry); + + if (usb_req->req.length > sizeof(*ddesc)) { + ret = -EOVERFLOW; + goto giveback_req; + } + + memcpy(ddesc, usb_req->req.buf, sizeof(*ddesc)); + udc->desc_cached = 1; + ret = 0; +giveback_req: + usb_req->req.status = 0; + usb_req->req.actual = usb_req->req.length; + usb_gadget_giveback_request(&(ep0->ep), &(usb_req->req)); +out: + return ret; +} + +/* + * Exposes device descriptor from the gadget driver. + */ +static ssize_t dev_desc_read(struct file *file, struct kobject *kobj, + struct bin_attribute *attr, char *out, + loff_t off, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct vudc *udc = (struct vudc *)dev_get_drvdata(dev); + char *desc_ptr = (char *) &udc->dev_desc; + unsigned long flags; + int ret; + + spin_lock_irqsave(&udc->lock, flags); + if (!udc->desc_cached) { + ret = -ENODEV; + goto unlock; + } + + memcpy(out, desc_ptr + off, count); + ret = count; +unlock: + spin_unlock_irqrestore(&udc->lock, flags); + return ret; +} +static BIN_ATTR_RO(dev_desc, sizeof(struct usb_device_descriptor)); + +static ssize_t store_sockfd(struct device *dev, struct device_attribute *attr, + const char *in, size_t count) +{ + struct vudc *udc = (struct vudc *) dev_get_drvdata(dev); + int rv; + int sockfd = 0; + int err; + struct socket *socket; + unsigned long flags; + int ret; + + rv = kstrtoint(in, 0, &sockfd); + if (rv != 0) + return -EINVAL; + + spin_lock_irqsave(&udc->lock, flags); + /* Don't export what we don't have */ + if (!udc || !udc->driver || !udc->pullup) { + dev_err(dev, "no device or gadget not bound"); + ret = -ENODEV; + goto unlock; + } + + if (sockfd != -1) { + if (udc->connected) { + dev_err(dev, "Device already connected"); + ret = -EBUSY; + goto unlock; + } + + spin_lock_irq(&udc->ud.lock); + + if (udc->ud.status != SDEV_ST_AVAILABLE) { + ret = -EINVAL; + goto unlock_ud; + } + + socket = sockfd_lookup(sockfd, &err); + if (!socket) { + dev_err(dev, "failed to lookup sock"); + ret = -EINVAL; + goto unlock_ud; + } + + udc->ud.tcp_socket = socket; + + spin_unlock_irq(&udc->ud.lock); + spin_unlock_irqrestore(&udc->lock, flags); + + udc->ud.tcp_rx = kthread_get_run(&v_rx_loop, + &udc->ud, "vudc_rx"); + udc->ud.tcp_tx = kthread_get_run(&v_tx_loop, + &udc->ud, "vudc_tx"); + + spin_lock_irqsave(&udc->lock, flags); + spin_lock_irq(&udc->ud.lock); + udc->ud.status = SDEV_ST_USED; + spin_unlock_irq(&udc->ud.lock); + + do_gettimeofday(&udc->start_time); + v_start_timer(udc); + udc->connected = 1; + } else { + if (!udc->connected) { + dev_err(dev, "Device not connected"); + ret = -EINVAL; + goto unlock; + } + + spin_lock_irq(&udc->ud.lock); + if (udc->ud.status != SDEV_ST_USED) { + ret = -EINVAL; + goto unlock_ud; + } + spin_unlock_irq(&udc->ud.lock); + + usbip_event_add(&udc->ud, VUDC_EVENT_DOWN); + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return count; + +unlock_ud: + spin_unlock_irq(&udc->ud.lock); +unlock: + spin_unlock_irqrestore(&udc->lock, flags); + + return ret; +} +static DEVICE_ATTR(usbip_sockfd, S_IWUSR, NULL, store_sockfd); + +static ssize_t usbip_status_show(struct device *dev, + struct device_attribute *attr, char *out) +{ + struct vudc *udc = (struct vudc *) dev_get_drvdata(dev); + int status; + + if (!udc) { + dev_err(dev, "no device"); + return -ENODEV; + } + spin_lock_irq(&udc->ud.lock); + status = udc->ud.status; + spin_unlock_irq(&udc->ud.lock); + + return snprintf(out, PAGE_SIZE, "%d\n", status); +} +static DEVICE_ATTR_RO(usbip_status); + +static struct attribute *dev_attrs[] = { + &dev_attr_usbip_sockfd.attr, + &dev_attr_usbip_status.attr, + NULL, +}; + +static struct bin_attribute *dev_bin_attrs[] = { + &bin_attr_dev_desc, + NULL, +}; + +const struct attribute_group vudc_attr_group = { + .attrs = dev_attrs, + .bin_attrs = dev_bin_attrs, +}; diff --git a/drivers/usb/usbip/vudc_transfer.c b/drivers/usb/usbip/vudc_transfer.c new file mode 100644 index 000000000000..aba6bd478045 --- /dev/null +++ b/drivers/usb/usbip/vudc_transfer.c @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2015 Karol Kosik <karo9@interia.eu> + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski <i.kotrasinsk@samsung.com> + * + * Based on dummy_hcd.c, which is: + * Copyright (C) 2003 David Brownell + * Copyright (C) 2003-2005 Alan Stern + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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/usb.h> +#include <linux/timer.h> +#include <linux/usb/ch9.h> + +#include "vudc.h" + +#define DEV_REQUEST (USB_TYPE_STANDARD | USB_RECIP_DEVICE) +#define DEV_INREQUEST (DEV_REQUEST | USB_DIR_IN) +#define INTF_REQUEST (USB_TYPE_STANDARD | USB_RECIP_INTERFACE) +#define INTF_INREQUEST (INTF_REQUEST | USB_DIR_IN) +#define EP_REQUEST (USB_TYPE_STANDARD | USB_RECIP_ENDPOINT) +#define EP_INREQUEST (EP_REQUEST | USB_DIR_IN) + +static int get_frame_limit(enum usb_device_speed speed) +{ + switch (speed) { + case USB_SPEED_LOW: + return 8 /*bytes*/ * 12 /*packets*/; + case USB_SPEED_FULL: + return 64 /*bytes*/ * 19 /*packets*/; + case USB_SPEED_HIGH: + return 512 /*bytes*/ * 13 /*packets*/ * 8 /*uframes*/; + case USB_SPEED_SUPER: + /* Bus speed is 500000 bytes/ms, so use a little less */ + return 490000; + default: + /* error */ + return -1; + } + +} + +/* + * handle_control_request() - handles all control transfers + * @udc: pointer to vudc + * @urb: the urb request to handle + * @setup: pointer to the setup data for a USB device control + * request + * @status: pointer to request handling status + * + * Return 0 - if the request was handled + * 1 - if the request wasn't handles + * error code on error + * + * Adapted from drivers/usb/gadget/udc/dummy_hcd.c + */ +static int handle_control_request(struct vudc *udc, struct urb *urb, + struct usb_ctrlrequest *setup, + int *status) +{ + struct vep *ep2; + int ret_val = 1; + unsigned w_index; + unsigned w_value; + + w_index = le16_to_cpu(setup->wIndex); + w_value = le16_to_cpu(setup->wValue); + switch (setup->bRequest) { + case USB_REQ_SET_ADDRESS: + if (setup->bRequestType != DEV_REQUEST) + break; + udc->address = w_value; + ret_val = 0; + *status = 0; + break; + case USB_REQ_SET_FEATURE: + if (setup->bRequestType == DEV_REQUEST) { + ret_val = 0; + switch (w_value) { + case USB_DEVICE_REMOTE_WAKEUP: + break; + case USB_DEVICE_B_HNP_ENABLE: + udc->gadget.b_hnp_enable = 1; + break; + case USB_DEVICE_A_HNP_SUPPORT: + udc->gadget.a_hnp_support = 1; + break; + case USB_DEVICE_A_ALT_HNP_SUPPORT: + udc->gadget.a_alt_hnp_support = 1; + break; + default: + ret_val = -EOPNOTSUPP; + } + if (ret_val == 0) { + udc->devstatus |= (1 << w_value); + *status = 0; + } + } else if (setup->bRequestType == EP_REQUEST) { + /* endpoint halt */ + ep2 = vudc_find_endpoint(udc, w_index); + if (!ep2 || ep2->ep.name == udc->ep[0].ep.name) { + ret_val = -EOPNOTSUPP; + break; + } + ep2->halted = 1; + ret_val = 0; + *status = 0; + } + break; + case USB_REQ_CLEAR_FEATURE: + if (setup->bRequestType == DEV_REQUEST) { + ret_val = 0; + switch (w_value) { + case USB_DEVICE_REMOTE_WAKEUP: + w_value = USB_DEVICE_REMOTE_WAKEUP; + break; + + case USB_DEVICE_U1_ENABLE: + case USB_DEVICE_U2_ENABLE: + case USB_DEVICE_LTM_ENABLE: + ret_val = -EOPNOTSUPP; + break; + default: + ret_val = -EOPNOTSUPP; + break; + } + if (ret_val == 0) { + udc->devstatus &= ~(1 << w_value); + *status = 0; + } + } else if (setup->bRequestType == EP_REQUEST) { + /* endpoint halt */ + ep2 = vudc_find_endpoint(udc, w_index); + if (!ep2) { + ret_val = -EOPNOTSUPP; + break; + } + if (!ep2->wedged) + ep2->halted = 0; + ret_val = 0; + *status = 0; + } + break; + case USB_REQ_GET_STATUS: + if (setup->bRequestType == DEV_INREQUEST + || setup->bRequestType == INTF_INREQUEST + || setup->bRequestType == EP_INREQUEST) { + char *buf; + /* + * device: remote wakeup, selfpowered + * interface: nothing + * endpoint: halt + */ + buf = (char *)urb->transfer_buffer; + if (urb->transfer_buffer_length > 0) { + if (setup->bRequestType == EP_INREQUEST) { + ep2 = vudc_find_endpoint(udc, w_index); + if (!ep2) { + ret_val = -EOPNOTSUPP; + break; + } + buf[0] = ep2->halted; + } else if (setup->bRequestType == + DEV_INREQUEST) { + buf[0] = (u8)udc->devstatus; + } else + buf[0] = 0; + } + if (urb->transfer_buffer_length > 1) + buf[1] = 0; + urb->actual_length = min_t(u32, 2, + urb->transfer_buffer_length); + ret_val = 0; + *status = 0; + } + break; + } + return ret_val; +} + +/* Adapted from dummy_hcd.c ; caller must hold lock */ +static int transfer(struct vudc *udc, + struct urb *urb, struct vep *ep, int limit) +{ + struct vrequest *req; + int sent = 0; +top: + /* if there's no request queued, the device is NAKing; return */ + list_for_each_entry(req, &ep->req_queue, req_entry) { + unsigned host_len, dev_len, len; + void *ubuf_pos, *rbuf_pos; + int is_short, to_host; + int rescan = 0; + + /* + * 1..N packets of ep->ep.maxpacket each ... the last one + * may be short (including zero length). + * + * writer can send a zlp explicitly (length 0) or implicitly + * (length mod maxpacket zero, and 'zero' flag); they always + * terminate reads. + */ + host_len = urb->transfer_buffer_length - urb->actual_length; + dev_len = req->req.length - req->req.actual; + len = min(host_len, dev_len); + + to_host = usb_pipein(urb->pipe); + if (unlikely(len == 0)) + is_short = 1; + else { + /* send multiple of maxpacket first, then remainder */ + if (len >= ep->ep.maxpacket) { + is_short = 0; + if (len % ep->ep.maxpacket > 0) + rescan = 1; + len -= len % ep->ep.maxpacket; + } else { + is_short = 1; + } + + ubuf_pos = urb->transfer_buffer + urb->actual_length; + rbuf_pos = req->req.buf + req->req.actual; + + if (urb->pipe & USB_DIR_IN) + memcpy(ubuf_pos, rbuf_pos, len); + else + memcpy(rbuf_pos, ubuf_pos, len); + + urb->actual_length += len; + req->req.actual += len; + sent += len; + } + + /* + * short packets terminate, maybe with overflow/underflow. + * it's only really an error to write too much. + * + * partially filling a buffer optionally blocks queue advances + * (so completion handlers can clean up the queue) but we don't + * need to emulate such data-in-flight. + */ + if (is_short) { + if (host_len == dev_len) { + req->req.status = 0; + urb->status = 0; + } else if (to_host) { + req->req.status = 0; + if (dev_len > host_len) + urb->status = -EOVERFLOW; + else + urb->status = 0; + } else { + urb->status = 0; + if (host_len > dev_len) + req->req.status = -EOVERFLOW; + else + req->req.status = 0; + } + + /* many requests terminate without a short packet */ + /* also check if we need to send zlp */ + } else { + if (req->req.length == req->req.actual) { + if (req->req.zero && to_host) + rescan = 1; + else + req->req.status = 0; + } + if (urb->transfer_buffer_length == urb->actual_length) { + if (urb->transfer_flags & URB_ZERO_PACKET && + !to_host) + rescan = 1; + else + urb->status = 0; + } + } + + /* device side completion --> continuable */ + if (req->req.status != -EINPROGRESS) { + + list_del_init(&req->req_entry); + spin_unlock(&udc->lock); + usb_gadget_giveback_request(&ep->ep, &req->req); + spin_lock(&udc->lock); + + /* requests might have been unlinked... */ + rescan = 1; + } + + /* host side completion --> terminate */ + if (urb->status != -EINPROGRESS) + break; + + /* rescan to continue with any other queued i/o */ + if (rescan) + goto top; + } + return sent; +} + +static void v_timer(unsigned long _vudc) +{ + struct vudc *udc = (struct vudc *) _vudc; + struct transfer_timer *timer = &udc->tr_timer; + struct urbp *urb_p, *tmp; + unsigned long flags; + struct usb_ep *_ep; + struct vep *ep; + int ret = 0; + int total, limit; + + spin_lock_irqsave(&udc->lock, flags); + + total = get_frame_limit(udc->gadget.speed); + if (total < 0) { /* unknown speed, or not set yet */ + timer->state = VUDC_TR_IDLE; + spin_unlock_irqrestore(&udc->lock, flags); + return; + } + /* is it next frame now? */ + if (time_after(jiffies, timer->frame_start + msecs_to_jiffies(1))) { + timer->frame_limit = total; + /* FIXME: how to make it accurate? */ + timer->frame_start = jiffies; + } else { + total = timer->frame_limit; + } + + list_for_each_entry(_ep, &udc->gadget.ep_list, ep_list) { + ep = to_vep(_ep); + ep->already_seen = 0; + } + +restart: + list_for_each_entry_safe(urb_p, tmp, &udc->urb_queue, urb_entry) { + struct urb *urb = urb_p->urb; + + ep = urb_p->ep; + if (urb->unlinked) + goto return_urb; + if (timer->state != VUDC_TR_RUNNING) + continue; + + if (!ep) { + urb->status = -EPROTO; + goto return_urb; + } + + /* Used up bandwidth? */ + if (total <= 0 && ep->type == USB_ENDPOINT_XFER_BULK) + continue; + + if (ep->already_seen) + continue; + ep->already_seen = 1; + if (ep == &udc->ep[0] && urb_p->new) { + ep->setup_stage = 1; + urb_p->new = 0; + } + if (ep->halted && !ep->setup_stage) { + urb->status = -EPIPE; + goto return_urb; + } + + if (ep == &udc->ep[0] && ep->setup_stage) { + /* TODO - flush any stale requests */ + ep->setup_stage = 0; + ep->halted = 0; + + ret = handle_control_request(udc, urb, + (struct usb_ctrlrequest *) urb->setup_packet, + (&urb->status)); + if (ret > 0) { + spin_unlock(&udc->lock); + ret = udc->driver->setup(&udc->gadget, + (struct usb_ctrlrequest *) + urb->setup_packet); + spin_lock(&udc->lock); + } + if (ret >= 0) { + /* no delays (max 64kb data stage) */ + limit = 64 * 1024; + goto treat_control_like_bulk; + } else { + urb->status = -EPIPE; + urb->actual_length = 0; + goto return_urb; + } + } + + limit = total; + switch (ep->type) { + case USB_ENDPOINT_XFER_ISOC: + /* TODO: support */ + urb->status = -EXDEV; + break; + + case USB_ENDPOINT_XFER_INT: + /* + * TODO: figure out bandwidth guarantees + * for now, give unlimited bandwidth + */ + limit += urb->transfer_buffer_length; + /* fallthrough */ + default: +treat_control_like_bulk: + total -= transfer(udc, urb, ep, limit); + } + if (urb->status == -EINPROGRESS) + continue; + +return_urb: + if (ep) + ep->already_seen = ep->setup_stage = 0; + + spin_lock(&udc->lock_tx); + list_del(&urb_p->urb_entry); + if (!urb->unlinked) { + v_enqueue_ret_submit(udc, urb_p); + } else { + v_enqueue_ret_unlink(udc, urb_p->seqnum, + urb->unlinked); + free_urbp_and_urb(urb_p); + } + wake_up(&udc->tx_waitq); + spin_unlock(&udc->lock_tx); + + goto restart; + } + + /* TODO - also wait on empty usb_request queues? */ + if (list_empty(&udc->urb_queue)) + timer->state = VUDC_TR_IDLE; + else + mod_timer(&timer->timer, + timer->frame_start + msecs_to_jiffies(1)); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +/* All timer functions are run with udc->lock held */ + +void v_init_timer(struct vudc *udc) +{ + struct transfer_timer *t = &udc->tr_timer; + + setup_timer(&t->timer, v_timer, (unsigned long) udc); + t->state = VUDC_TR_STOPPED; +} + +void v_start_timer(struct vudc *udc) +{ + struct transfer_timer *t = &udc->tr_timer; + + dev_dbg(&udc->pdev->dev, "timer start"); + switch (t->state) { + case VUDC_TR_RUNNING: + return; + case VUDC_TR_IDLE: + return v_kick_timer(udc, jiffies); + case VUDC_TR_STOPPED: + t->state = VUDC_TR_IDLE; + t->frame_start = jiffies; + t->frame_limit = get_frame_limit(udc->gadget.speed); + return v_kick_timer(udc, jiffies); + } +} + +void v_kick_timer(struct vudc *udc, unsigned long time) +{ + struct transfer_timer *t = &udc->tr_timer; + + dev_dbg(&udc->pdev->dev, "timer kick"); + switch (t->state) { + case VUDC_TR_RUNNING: + return; + case VUDC_TR_IDLE: + t->state = VUDC_TR_RUNNING; + /* fallthrough */ + case VUDC_TR_STOPPED: + /* we may want to kick timer to unqueue urbs */ + mod_timer(&t->timer, time); + } +} + +void v_stop_timer(struct vudc *udc) +{ + struct transfer_timer *t = &udc->tr_timer; + + /* timer itself will take care of stopping */ + dev_dbg(&udc->pdev->dev, "timer stop"); + t->state = VUDC_TR_STOPPED; +} diff --git a/drivers/usb/usbip/vudc_tx.c b/drivers/usb/usbip/vudc_tx.c new file mode 100644 index 000000000000..234661782fa0 --- /dev/null +++ b/drivers/usb/usbip/vudc_tx.c @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2015 Karol Kosik <karo9@interia.eu> + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski <i.kotrasinsk@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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 <net/sock.h> +#include <linux/list.h> +#include <linux/kthread.h> + +#include "usbip_common.h" +#include "vudc.h" + +static inline void setup_base_pdu(struct usbip_header_basic *base, + __u32 command, __u32 seqnum) +{ + base->command = command; + base->seqnum = seqnum; + base->devid = 0; + base->ep = 0; + base->direction = 0; +} + +static void setup_ret_submit_pdu(struct usbip_header *rpdu, struct urbp *urb_p) +{ + setup_base_pdu(&rpdu->base, USBIP_RET_SUBMIT, urb_p->seqnum); + usbip_pack_pdu(rpdu, urb_p->urb, USBIP_RET_SUBMIT, 1); +} + +static void setup_ret_unlink_pdu(struct usbip_header *rpdu, + struct v_unlink *unlink) +{ + setup_base_pdu(&rpdu->base, USBIP_RET_UNLINK, unlink->seqnum); + rpdu->u.ret_unlink.status = unlink->status; +} + +static int v_send_ret_unlink(struct vudc *udc, struct v_unlink *unlink) +{ + struct msghdr msg; + struct kvec iov[1]; + size_t txsize; + + int ret; + struct usbip_header pdu_header; + + txsize = 0; + memset(&pdu_header, 0, sizeof(pdu_header)); + memset(&msg, 0, sizeof(msg)); + memset(&iov, 0, sizeof(iov)); + + /* 1. setup usbip_header */ + setup_ret_unlink_pdu(&pdu_header, unlink); + usbip_header_correct_endian(&pdu_header, 1); + + iov[0].iov_base = &pdu_header; + iov[0].iov_len = sizeof(pdu_header); + txsize += sizeof(pdu_header); + + ret = kernel_sendmsg(udc->ud.tcp_socket, &msg, iov, + 1, txsize); + if (ret != txsize) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_TCP); + if (ret >= 0) + return -EPIPE; + return ret; + } + kfree(unlink); + + return txsize; +} + +static int v_send_ret_submit(struct vudc *udc, struct urbp *urb_p) +{ + struct urb *urb = urb_p->urb; + struct usbip_header pdu_header; + struct usbip_iso_packet_descriptor *iso_buffer = NULL; + struct kvec *iov = NULL; + int iovnum = 0; + int ret = 0; + size_t txsize; + struct msghdr msg; + + txsize = 0; + memset(&pdu_header, 0, sizeof(pdu_header)); + memset(&msg, 0, sizeof(msg)); + + if (urb_p->type == USB_ENDPOINT_XFER_ISOC) + iovnum = 2 + urb->number_of_packets; + else + iovnum = 2; + + iov = kcalloc(iovnum, sizeof(*iov), GFP_KERNEL); + if (!iov) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_MALLOC); + ret = -ENOMEM; + goto out; + } + iovnum = 0; + + /* 1. setup usbip_header */ + setup_ret_submit_pdu(&pdu_header, urb_p); + usbip_dbg_stub_tx("setup txdata seqnum: %d urb: %p\n", + pdu_header.base.seqnum, urb); + usbip_header_correct_endian(&pdu_header, 1); + + iov[iovnum].iov_base = &pdu_header; + iov[iovnum].iov_len = sizeof(pdu_header); + iovnum++; + txsize += sizeof(pdu_header); + + /* 2. setup transfer buffer */ + if (urb_p->type != USB_ENDPOINT_XFER_ISOC && + usb_pipein(urb->pipe) && urb->actual_length > 0) { + iov[iovnum].iov_base = urb->transfer_buffer; + iov[iovnum].iov_len = urb->actual_length; + iovnum++; + txsize += urb->actual_length; + } else if (urb_p->type == USB_ENDPOINT_XFER_ISOC && + usb_pipein(urb->pipe)) { + /* FIXME - copypasted from stub_tx, refactor */ + int i; + + for (i = 0; i < urb->number_of_packets; i++) { + iov[iovnum].iov_base = urb->transfer_buffer + + urb->iso_frame_desc[i].offset; + iov[iovnum].iov_len = + urb->iso_frame_desc[i].actual_length; + iovnum++; + txsize += urb->iso_frame_desc[i].actual_length; + } + + if (txsize != sizeof(pdu_header) + urb->actual_length) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_TCP); + ret = -EPIPE; + goto out; + } + } + /* else - no buffer to send */ + + /* 3. setup iso_packet_descriptor */ + if (urb_p->type == USB_ENDPOINT_XFER_ISOC) { + ssize_t len = 0; + + iso_buffer = usbip_alloc_iso_desc_pdu(urb, &len); + if (!iso_buffer) { + usbip_event_add(&udc->ud, + VUDC_EVENT_ERROR_MALLOC); + ret = -ENOMEM; + goto out; + } + + iov[iovnum].iov_base = iso_buffer; + iov[iovnum].iov_len = len; + txsize += len; + iovnum++; + } + + ret = kernel_sendmsg(udc->ud.tcp_socket, &msg, + iov, iovnum, txsize); + if (ret != txsize) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_TCP); + if (ret >= 0) + ret = -EPIPE; + goto out; + } + +out: + kfree(iov); + kfree(iso_buffer); + free_urbp_and_urb(urb_p); + if (ret < 0) + return ret; + return txsize; +} + +static int v_send_ret(struct vudc *udc) +{ + unsigned long flags; + struct tx_item *txi; + size_t total_size = 0; + int ret = 0; + + spin_lock_irqsave(&udc->lock_tx, flags); + while (!list_empty(&udc->tx_queue)) { + txi = list_first_entry(&udc->tx_queue, struct tx_item, + tx_entry); + list_del(&txi->tx_entry); + spin_unlock_irqrestore(&udc->lock_tx, flags); + + switch (txi->type) { + case TX_SUBMIT: + ret = v_send_ret_submit(udc, txi->s); + break; + case TX_UNLINK: + ret = v_send_ret_unlink(udc, txi->u); + break; + } + kfree(txi); + + if (ret < 0) + return ret; + + total_size += ret; + + spin_lock_irqsave(&udc->lock_tx, flags); + } + + spin_unlock_irqrestore(&udc->lock_tx, flags); + return total_size; +} + + +int v_tx_loop(void *data) +{ + struct usbip_device *ud = (struct usbip_device *) data; + struct vudc *udc = container_of(ud, struct vudc, ud); + int ret; + + while (!kthread_should_stop()) { + if (usbip_event_happened(&udc->ud)) + break; + ret = v_send_ret(udc); + if (ret < 0) { + pr_warn("v_tx exit with error %d", ret); + break; + } + wait_event_interruptible(udc->tx_waitq, + (!list_empty(&udc->tx_queue) || + kthread_should_stop())); + } + + return 0; +} + +/* called with spinlocks held */ +void v_enqueue_ret_unlink(struct vudc *udc, __u32 seqnum, __u32 status) +{ + struct tx_item *txi; + struct v_unlink *unlink; + + txi = kzalloc(sizeof(*txi), GFP_ATOMIC); + if (!txi) { + usbip_event_add(&udc->ud, VDEV_EVENT_ERROR_MALLOC); + return; + } + unlink = kzalloc(sizeof(*unlink), GFP_ATOMIC); + if (!unlink) { + kfree(txi); + usbip_event_add(&udc->ud, VDEV_EVENT_ERROR_MALLOC); + return; + } + + unlink->seqnum = seqnum; + unlink->status = status; + txi->type = TX_UNLINK; + txi->u = unlink; + + list_add_tail(&txi->tx_entry, &udc->tx_queue); +} + +/* called with spinlocks held */ +void v_enqueue_ret_submit(struct vudc *udc, struct urbp *urb_p) +{ + struct tx_item *txi; + + txi = kzalloc(sizeof(*txi), GFP_ATOMIC); + if (!txi) { + usbip_event_add(&udc->ud, VDEV_EVENT_ERROR_MALLOC); + return; + } + + txi->type = TX_SUBMIT; + txi->s = urb_p; + + list_add_tail(&txi->tx_entry, &udc->tx_queue); +} |