244 lines
5.5 KiB
C
244 lines
5.5 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>
|
|
*/
|
|
|
|
#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_irqsave(&udc->lock, flags);
|
|
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_irqrestore(&udc->lock, flags);
|
|
usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_TCP);
|
|
ret = -EPIPE;
|
|
goto free_urbp;
|
|
}
|
|
urb_p->type = urb_p->ep->type;
|
|
spin_unlock_irqrestore(&udc->lock, flags);
|
|
|
|
urb_p->new = 1;
|
|
urb_p->seqnum = pdu->base.seqnum;
|
|
|
|
if (urb_p->ep->type == USB_ENDPOINT_XFER_ISOC) {
|
|
/* validate packet size and number of packets */
|
|
unsigned int maxp, packets, bytes;
|
|
|
|
maxp = usb_endpoint_maxp(urb_p->ep->desc);
|
|
maxp *= usb_endpoint_maxp_mult(urb_p->ep->desc);
|
|
bytes = pdu->u.cmd_submit.transfer_buffer_length;
|
|
packets = DIV_ROUND_UP(bytes, maxp);
|
|
|
|
if (pdu->u.cmd_submit.number_of_packets < 0 ||
|
|
pdu->u.cmd_submit.number_of_packets > packets) {
|
|
dev_err(&udc->gadget.dev,
|
|
"CMD_SUBMIT: isoc invalid num packets %d\n",
|
|
pdu->u.cmd_submit.number_of_packets);
|
|
ret = -EMSGSIZE;
|
|
goto free_urbp;
|
|
}
|
|
}
|
|
|
|
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 */
|
|
BUILD_BUG_ON_MSG(PIPE_BULK != 3, "PIPE_* doesn't range from 0 to 3");
|
|
|
|
urb_p->urb->pipe &= ~(PIPE_BULK << 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;
|
|
}
|