mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-15 00:04:15 +08:00
b9a24821c7
The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Link: https://lore.kernel.org/r/20231103171428.3636570-2-u.kleine-koenig@pengutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
639 lines
13 KiB
C
639 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 vrequest *req;
|
|
|
|
if (!_ep)
|
|
return NULL;
|
|
|
|
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;
|
|
|
|
/* ep is always valid here - see usb_ep_free_request() */
|
|
if (!_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;
|
|
|
|
udc_dev = kzalloc(sizeof(*udc_dev), GFP_KERNEL);
|
|
if (!udc_dev)
|
|
return NULL;
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
mutex_init(&ud->sysfs_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;
|
|
|
|
platform_set_drvdata(pdev, udc);
|
|
|
|
return ret;
|
|
|
|
err_add_udc:
|
|
cleanup_vudc_hw(udc);
|
|
err_init_vudc_hw:
|
|
kfree(udc);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
void vudc_remove(struct platform_device *pdev)
|
|
{
|
|
struct vudc *udc = platform_get_drvdata(pdev);
|
|
|
|
usb_del_gadget_udc(&udc->gadget);
|
|
cleanup_vudc_hw(udc);
|
|
kfree(udc);
|
|
}
|