forked from Minki/linux
7f2b019c8d
Now that the SPDX tag is in all USB files, that identifies the license in a specific and legally-defined manner. So the extra GPL text wording can be removed as it is no longer needed at all. This is done on a quest to remove the 700+ different ways that files in the kernel describe the GPL license text. And there's unneeded stuff like the address (sometimes incorrect) for the FSF which is never needed. No copyright headers or other non-license-description text was removed. Cc: Valentina Manea <valentina.manea.m@gmail.com> Acked-by: Shuah Khan <shuahkh@osg.samsung.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
650 lines
13 KiB
C
650 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* 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>
|
|
*/
|
|
|
|
#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 */
|
|
|
|
static int vgadget_get_frame(struct usb_gadget *_gadget)
|
|
{
|
|
struct timespec64 now;
|
|
struct vudc *udc = usb_gadget_to_vudc(_gadget);
|
|
|
|
ktime_get_ts64(&now);
|
|
return ((now.tv_sec - udc->start_time.tv_sec) * 1000 +
|
|
(now.tv_nsec - udc->start_time.tv_nsec) / NSEC_PER_MSEC)
|
|
& 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 int 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);
|
|
_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_rx) {
|
|
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;
|
|
|
|
ep->ep.ops = &vep_ops;
|
|
|
|
usb_ep_set_maxpacket_limit(&ep->ep, ~0);
|
|
ep->ep.max_streams = 16;
|
|
ep->gadget = &udc->gadget;
|
|
INIT_LIST_HEAD(&ep->req_queue);
|
|
|
|
if (i == 0) {
|
|
/* ep0 */
|
|
ep->ep.caps.type_control = true;
|
|
ep->ep.caps.dir_out = true;
|
|
ep->ep.caps.dir_in = true;
|
|
|
|
udc->gadget.ep0 = &ep->ep;
|
|
} else {
|
|
/* All other eps */
|
|
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;
|
|
|
|
list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|