root/drivers/usb/usbip/vhci_rx.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2003-2008 Takahiro Hirofuchi
 */

#include <linux/kthread.h>
#include <linux/slab.h>

#include "usbip_common.h"
#include "vhci.h"

/* get URB from transmitted urb queue. caller must hold vdev->priv_lock */
struct urb *pickup_urb_and_free_priv(struct vhci_device *vdev, __u32 seqnum)
{
        struct vhci_priv *priv, *tmp;
        struct urb *urb = NULL;
        int status;

        list_for_each_entry_safe(priv, tmp, &vdev->priv_rx, list) {
                if (priv->seqnum != seqnum)
                        continue;

                urb = priv->urb;
                status = urb->status;

                usbip_dbg_vhci_rx("find urb seqnum %u\n", seqnum);

                switch (status) {
                case -ENOENT:
                        fallthrough;
                case -ECONNRESET:
                        dev_dbg(&urb->dev->dev,
                                 "urb seq# %u was unlinked %ssynchronously\n",
                                 seqnum, status == -ENOENT ? "" : "a");
                        break;
                case -EINPROGRESS:
                        /* no info output */
                        break;
                default:
                        dev_dbg(&urb->dev->dev,
                                 "urb seq# %u may be in a error, status %d\n",
                                 seqnum, status);
                }

                list_del(&priv->list);
                kfree(priv);
                urb->hcpriv = NULL;

                break;
        }

        return urb;
}

static void vhci_recv_ret_submit(struct vhci_device *vdev,
                                 struct usbip_header *pdu)
{
        struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev);
        struct vhci *vhci = vhci_hcd->vhci;
        struct usbip_device *ud = &vdev->ud;
        struct urb *urb;
        unsigned long flags;

        spin_lock_irqsave(&vdev->priv_lock, flags);
        urb = pickup_urb_and_free_priv(vdev, pdu->base.seqnum);
        spin_unlock_irqrestore(&vdev->priv_lock, flags);

        if (!urb) {
                pr_err("cannot find a urb of seqnum %u max seqnum %u\n",
                        pdu->base.seqnum,
                        atomic_read(&vhci_hcd->seqnum));
                usbip_event_add(ud, VDEV_EVENT_ERROR_TCP);
                return;
        }

        /* unpack the pdu to a urb */
        usbip_pack_pdu(pdu, urb, USBIP_RET_SUBMIT, 0);

        /* recv transfer buffer */
        if (usbip_recv_xbuff(ud, urb) < 0) {
                urb->status = -EPROTO;
                goto error;
        }

        /* recv iso_packet_descriptor */
        if (usbip_recv_iso(ud, urb) < 0) {
                urb->status = -EPROTO;
                goto error;
        }

        /* restore the padding in iso packets */
        usbip_pad_iso(ud, urb);

error:
        if (usbip_dbg_flag_vhci_rx)
                usbip_dump_urb(urb);

        if (urb->num_sgs)
                urb->transfer_flags &= ~URB_DMA_MAP_SG;

        usbip_dbg_vhci_rx("now giveback urb %u\n", pdu->base.seqnum);

        spin_lock_irqsave(&vhci->lock, flags);
        usb_hcd_unlink_urb_from_ep(vhci_hcd_to_hcd(vhci_hcd), urb);
        spin_unlock_irqrestore(&vhci->lock, flags);

        usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci_hcd), urb, urb->status);

        usbip_dbg_vhci_rx("Leave\n");
}

static struct vhci_unlink *dequeue_pending_unlink(struct vhci_device *vdev,
                                                  struct usbip_header *pdu)
{
        struct vhci_unlink *unlink, *tmp;
        unsigned long flags;

        spin_lock_irqsave(&vdev->priv_lock, flags);

        list_for_each_entry_safe(unlink, tmp, &vdev->unlink_rx, list) {
                pr_info("unlink->seqnum %lu\n", unlink->seqnum);
                if (unlink->seqnum == pdu->base.seqnum) {
                        usbip_dbg_vhci_rx("found pending unlink, %lu\n",
                                          unlink->seqnum);
                        list_del(&unlink->list);

                        spin_unlock_irqrestore(&vdev->priv_lock, flags);
                        return unlink;
                }
        }

        spin_unlock_irqrestore(&vdev->priv_lock, flags);

        return NULL;
}

static void vhci_recv_ret_unlink(struct vhci_device *vdev,
                                 struct usbip_header *pdu)
{
        struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev);
        struct vhci *vhci = vhci_hcd->vhci;
        struct vhci_unlink *unlink;
        struct urb *urb;
        unsigned long flags;

        usbip_dump_header(pdu);

        unlink = dequeue_pending_unlink(vdev, pdu);
        if (!unlink) {
                pr_info("cannot find the pending unlink %u\n",
                        pdu->base.seqnum);
                return;
        }

        spin_lock_irqsave(&vdev->priv_lock, flags);
        urb = pickup_urb_and_free_priv(vdev, unlink->unlink_seqnum);
        spin_unlock_irqrestore(&vdev->priv_lock, flags);

        if (!urb) {
                /*
                 * I get the result of a unlink request. But, it seems that I
                 * already received the result of its submit result and gave
                 * back the URB.
                 */
                pr_info("the urb (seqnum %u) was already given back\n",
                        pdu->base.seqnum);
        } else {
                usbip_dbg_vhci_rx("now giveback urb %u\n", pdu->base.seqnum);

                /* If unlink is successful, status is -ECONNRESET */
                urb->status = pdu->u.ret_unlink.status;
                pr_info("urb->status %d\n", urb->status);

                spin_lock_irqsave(&vhci->lock, flags);
                usb_hcd_unlink_urb_from_ep(vhci_hcd_to_hcd(vhci_hcd), urb);
                spin_unlock_irqrestore(&vhci->lock, flags);

                usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci_hcd), urb, urb->status);
        }

        kfree(unlink);
}

static int vhci_priv_tx_empty(struct vhci_device *vdev)
{
        int empty = 0;
        unsigned long flags;

        spin_lock_irqsave(&vdev->priv_lock, flags);
        empty = list_empty(&vdev->priv_rx);
        spin_unlock_irqrestore(&vdev->priv_lock, flags);

        return empty;
}

/* recv a pdu */
static void vhci_rx_pdu(struct usbip_device *ud)
{
        int ret;
        struct usbip_header pdu;
        struct vhci_device *vdev = container_of(ud, struct vhci_device, ud);

        usbip_dbg_vhci_rx("Enter\n");

        memset(&pdu, 0, sizeof(pdu));

        /* receive a pdu header */
        ret = usbip_recv(ud->tcp_socket, &pdu, sizeof(pdu));
        if (ret < 0) {
                if (ret == -ECONNRESET)
                        pr_info("connection reset by peer\n");
                else if (ret == -EAGAIN) {
                        /* ignore if connection was idle */
                        if (vhci_priv_tx_empty(vdev))
                                return;
                        pr_info("connection timed out with pending urbs\n");
                } else if (ret != -ERESTARTSYS)
                        pr_info("xmit failed %d\n", ret);

                usbip_event_add(ud, VDEV_EVENT_ERROR_TCP);
                return;
        }
        if (ret == 0) {
                pr_info("connection closed");
                usbip_event_add(ud, VDEV_EVENT_DOWN);
                return;
        }
        if (ret != sizeof(pdu)) {
                pr_err("received pdu size is %d, should be %d\n", ret,
                       (unsigned int)sizeof(pdu));
                usbip_event_add(ud, VDEV_EVENT_ERROR_TCP);
                return;
        }

        usbip_header_correct_endian(&pdu, 0);

        if (usbip_dbg_flag_vhci_rx)
                usbip_dump_header(&pdu);

        switch (pdu.base.command) {
        case USBIP_RET_SUBMIT:
                vhci_recv_ret_submit(vdev, &pdu);
                break;
        case USBIP_RET_UNLINK:
                vhci_recv_ret_unlink(vdev, &pdu);
                break;
        default:
                /* NOT REACHED */
                pr_err("unknown pdu %u\n", pdu.base.command);
                usbip_dump_header(&pdu);
                usbip_event_add(ud, VDEV_EVENT_ERROR_TCP);
                break;
        }
}

int vhci_rx_loop(void *data)
{
        struct usbip_device *ud = data;

        while (!kthread_should_stop()) {
                if (usbip_event_happened(ud))
                        break;

                usbip_kcov_remote_start(ud);
                vhci_rx_pdu(ud);
                usbip_kcov_remote_stop();
        }

        return 0;
}