![Krzysztof Opasiak](/assets/img/avatar_default.png)
Current implementation of init_vudc_hw() adds ep0 to ep_list and then after looping through all endpoints removes it from that list. As this may be misleading let's refactor this function and avoid adding and removing ep0 to eplist and place it immediately in correct place. In addition let's remove redundant 0 assignments as ep array is zeroed during allocation. Signed-off-by: Krzysztof Opasiak <k.opasiak@samsung.com> Acked-by: Shuah Khan <shuahkh@osg.samsung.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
663 lines
14 KiB
C
663 lines
14 KiB
C
/*
|
|
* 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 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;
|
|
}
|