mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-18 17:54:13 +08:00
0775a9cbc6
Modification to Kconfig, vhci_hc.c, vhci.h and vhci_sysfs.c. 1. kernel config Followings are added. USBIP_VHCI_HC_PORTS: Number of ports per USB/IP virtual host controller. The default is 8 - same as current VHCI_NPORTS. USBIP_VHCI_NR_HCS: Number of USB/IP virtual host controllers. The default is 1. This paratmeter is replaced with USBIP_VHCI_INIT_HCS and USBIP_VHCI_MAX_HCS included in succeeding dynamic extension patch. 2. the_controller to controllers the_controller is changed to vhci_pdevs: array of struct platform_device. 3. vhci_sysfs.c Sysfs structure is changed as following. BEFORE: /sys/devices/platform +-- vhci +-- status +-- attach +-- detach +-- usbip_debug AFTER: example for CONFIG_USBIP_NR_HCS=4 /sys/devices/platform +-- vhci | +-- nports | +-- status | +-- status.1 | +-- status.2 | +-- status.3 | +-- attach | +-- detach | +-- usbip_debug +-- vhci.1 +-- vhci.2 +-- vhci.3 vhci[.N] is shown for each host controller kobj. vhch.1, vhci.2, ... are shown only when CONFIG_USBIP_NR_HCS is more than 1. Only 'vhci' (without number) has user space interfaces. 'nports' is newly added to give ports-per-controller and number of controlles. Before that, number of ports is acquired by reading status lines. Status is divided for each controller to avoid page size (4KB) limitation. Old userspace tool binaries work with the first status within the first controller. Inconsistency between status header and content is fixed. 4th and 5th column are header: "dev bus" content(unused): "000 000" content(used): "%08x", devid Only 1st and 2nd column are used by program. In old version, sscanf() in parse_status expect no bus column. And bus_id string is shown in the last column. Then bus in the header is removed and unused content is replaced with 8 zeros. The sscanf() expects more than 5 columns and new has 6 columns so there's no compatibility issue in this change. Signed-off-by: Nobuo Iwata <nobuo.iwata@fujixerox.co.jp> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
274 lines
6.5 KiB
C
274 lines
6.5 KiB
C
/*
|
|
* Copyright (C) 2003-2008 Takahiro Hirofuchi
|
|
*
|
|
* This 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 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
|
|
* USA.
|
|
*/
|
|
|
|
#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 %p vurb %p seqnum %u\n",
|
|
urb, priv, seqnum);
|
|
|
|
switch (status) {
|
|
case -ENOENT:
|
|
/* fall through */
|
|
case -ECONNRESET:
|
|
dev_info(&urb->dev->dev,
|
|
"urb %p was unlinked %ssynchronuously.\n", urb,
|
|
status == -ENOENT ? "" : "a");
|
|
break;
|
|
case -EINPROGRESS:
|
|
/* no info output */
|
|
break;
|
|
default:
|
|
dev_info(&urb->dev->dev,
|
|
"urb %p may be in a error, status %d\n", urb,
|
|
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 = vdev_to_vhci(vdev);
|
|
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\n", pdu->base.seqnum);
|
|
pr_info("max seqnum %d\n",
|
|
atomic_read(&vhci->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)
|
|
return;
|
|
|
|
/* recv iso_packet_descriptor */
|
|
if (usbip_recv_iso(ud, urb) < 0)
|
|
return;
|
|
|
|
/* restore the padding in iso packets */
|
|
usbip_pad_iso(ud, urb);
|
|
|
|
if (usbip_dbg_flag_vhci_rx)
|
|
usbip_dump_urb(urb);
|
|
|
|
usbip_dbg_vhci_rx("now giveback urb %p\n", urb);
|
|
|
|
spin_lock_irqsave(&vhci->lock, flags);
|
|
usb_hcd_unlink_urb_from_ep(vhci_to_hcd(vhci), urb);
|
|
spin_unlock_irqrestore(&vhci->lock, flags);
|
|
|
|
usb_hcd_giveback_urb(vhci_to_hcd(vhci), 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 = vdev_to_vhci(vdev);
|
|
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 %d) was already given back\n",
|
|
pdu->base.seqnum);
|
|
} else {
|
|
usbip_dbg_vhci_rx("now giveback urb %p\n", urb);
|
|
|
|
/* 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_to_hcd(vhci), urb);
|
|
spin_unlock_irqrestore(&vhci->lock, flags);
|
|
|
|
usb_hcd_giveback_urb(vhci_to_hcd(vhci), 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;
|
|
|
|
vhci_rx_pdu(ud);
|
|
}
|
|
|
|
return 0;
|
|
}
|