forked from Minki/linux
Staging: USB/IP: add client driver
This adds the USB IP client driver Brian Merrell cleaned up a lot of this code and submitted it for inclusion. Greg also did a lot of cleanup. Signed-off-by: Brian G. Merrell <bgmerrell@novell.com> Cc: Takahiro Hirofuchi <hirofuchi@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
parent
05a1f28e87
commit
04679b3489
@ -12,3 +12,14 @@ config USB_IP_COMMON
|
||||
module will be called usbip_common_mod.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config USB_IP_VHCI_HCD
|
||||
tristate "USB IP client driver"
|
||||
depends on USB_IP_COMMON
|
||||
default N
|
||||
---help---
|
||||
This enables the USB IP host controller driver which will
|
||||
run on the client machine.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called vhci_hcd.
|
||||
|
@ -1,6 +1,9 @@
|
||||
obj-$(CONFIG_USB_IP_COMMON) += usbip_common_mod.o
|
||||
usbip_common_mod-objs := usbip_common.o usbip_event.o
|
||||
|
||||
obj-$(CONFIG_USB_IP_VHCI_HCD) += vhci-hcd.o
|
||||
vhci-hcd-objs := vhci_sysfs.o vhci_tx.o vhci_rx.o vhci_hcd.o
|
||||
|
||||
ifeq ($(CONFIG_USB_DEBUG),y)
|
||||
EXTRA_CFLAGS += -DDEBUG
|
||||
endif
|
||||
|
142
drivers/staging/usbip/vhci.h
Normal file
142
drivers/staging/usbip/vhci.h
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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/platform_device.h>
|
||||
#include "../../usb/core/hcd.h"
|
||||
|
||||
|
||||
struct vhci_device {
|
||||
struct usb_device *udev;
|
||||
|
||||
/*
|
||||
* devid specifies a remote usb device uniquely instead
|
||||
* of combination of busnum and devnum.
|
||||
*/
|
||||
__u32 devid;
|
||||
|
||||
/* speed of a remote device */
|
||||
enum usb_device_speed speed;
|
||||
|
||||
/* vhci root-hub port to which this device is attached */
|
||||
__u32 rhport;
|
||||
|
||||
struct usbip_device ud;
|
||||
|
||||
|
||||
/* lock for the below link lists */
|
||||
spinlock_t priv_lock;
|
||||
|
||||
/* vhci_priv is linked to one of them. */
|
||||
struct list_head priv_tx;
|
||||
struct list_head priv_rx;
|
||||
|
||||
/* vhci_unlink is linked to one of them */
|
||||
struct list_head unlink_tx;
|
||||
struct list_head unlink_rx;
|
||||
|
||||
/* vhci_tx thread sleeps for this queue */
|
||||
wait_queue_head_t waitq_tx;
|
||||
};
|
||||
|
||||
|
||||
/* urb->hcpriv, use container_of() */
|
||||
struct vhci_priv {
|
||||
unsigned long seqnum;
|
||||
struct list_head list;
|
||||
|
||||
struct vhci_device *vdev;
|
||||
struct urb *urb;
|
||||
};
|
||||
|
||||
|
||||
struct vhci_unlink {
|
||||
/* seqnum of this request */
|
||||
unsigned long seqnum;
|
||||
|
||||
struct list_head list;
|
||||
|
||||
/* seqnum of the unlink target */
|
||||
unsigned long unlink_seqnum;
|
||||
};
|
||||
|
||||
/*
|
||||
* The number of ports is less than 16 ?
|
||||
* USB_MAXCHILDREN is statically defined to 16 in usb.h. Its maximum value
|
||||
* would be 31 because the event_bits[1] of struct usb_hub is defined as
|
||||
* unsigned long in hub.h
|
||||
*/
|
||||
#define VHCI_NPORTS 8
|
||||
|
||||
/* for usb_bus.hcpriv */
|
||||
struct vhci_hcd {
|
||||
spinlock_t lock;
|
||||
|
||||
u32 port_status[VHCI_NPORTS];
|
||||
|
||||
unsigned resuming:1;
|
||||
unsigned long re_timeout;
|
||||
|
||||
atomic_t seqnum;
|
||||
|
||||
/*
|
||||
* NOTE:
|
||||
* wIndex shows the port number and begins from 1.
|
||||
* But, the index of this array begins from 0.
|
||||
*/
|
||||
struct vhci_device vdev[VHCI_NPORTS];
|
||||
|
||||
/* vhci_device which has not been assiged its address yet */
|
||||
int pending_port;
|
||||
};
|
||||
|
||||
|
||||
extern struct vhci_hcd *the_controller;
|
||||
extern struct attribute_group dev_attr_group;
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
/* prototype declaration */
|
||||
|
||||
/* vhci_hcd.c */
|
||||
void rh_port_connect(int rhport, enum usb_device_speed speed);
|
||||
void rh_port_disconnect(int rhport);
|
||||
void vhci_rx_loop(struct usbip_task *ut);
|
||||
void vhci_tx_loop(struct usbip_task *ut);
|
||||
|
||||
#define hardware (&the_controller->pdev.dev)
|
||||
|
||||
static inline struct vhci_device *port_to_vdev(__u32 port)
|
||||
{
|
||||
return &the_controller->vdev[port];
|
||||
}
|
||||
|
||||
static inline struct vhci_hcd *hcd_to_vhci(struct usb_hcd *hcd)
|
||||
{
|
||||
return (struct vhci_hcd *) (hcd->hcd_priv);
|
||||
}
|
||||
|
||||
static inline struct usb_hcd *vhci_to_hcd(struct vhci_hcd *vhci)
|
||||
{
|
||||
return container_of((void *) vhci, struct usb_hcd, hcd_priv);
|
||||
}
|
||||
|
||||
static inline struct device *vhci_dev(struct vhci_hcd *vhci)
|
||||
{
|
||||
return vhci_to_hcd(vhci)->self.controller;
|
||||
}
|
1275
drivers/staging/usbip/vhci_hcd.c
Normal file
1275
drivers/staging/usbip/vhci_hcd.c
Normal file
File diff suppressed because it is too large
Load Diff
251
drivers/staging/usbip/vhci_rx.c
Normal file
251
drivers/staging/usbip/vhci_rx.c
Normal file
@ -0,0 +1,251 @@
|
||||
/*
|
||||
* 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 "usbip_common.h"
|
||||
#include "vhci.h"
|
||||
|
||||
|
||||
/* get URB from transmitted urb queue */
|
||||
static struct urb *pickup_urb_and_free_priv(struct vhci_device *vdev,
|
||||
__u32 seqnum)
|
||||
{
|
||||
struct vhci_priv *priv, *tmp;
|
||||
struct urb *urb = NULL;
|
||||
int status;
|
||||
|
||||
spin_lock(&vdev->priv_lock);
|
||||
|
||||
list_for_each_entry_safe(priv, tmp, &vdev->priv_rx, list) {
|
||||
if (priv->seqnum == seqnum) {
|
||||
urb = priv->urb;
|
||||
status = urb->status;
|
||||
|
||||
dbg_vhci_rx("find urb %p vurb %p seqnum %u\n",
|
||||
urb, priv, seqnum);
|
||||
|
||||
/* TODO: fix logic here to improve indent situtation */
|
||||
if (status != -EINPROGRESS) {
|
||||
if (status == -ENOENT ||
|
||||
status == -ECONNRESET)
|
||||
dev_info(&urb->dev->dev,
|
||||
"urb %p was unlinked "
|
||||
"%ssynchronuously.\n", urb,
|
||||
status == -ENOENT ? "" : "a");
|
||||
else
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock(&vdev->priv_lock);
|
||||
|
||||
return urb;
|
||||
}
|
||||
|
||||
static void vhci_recv_ret_submit(struct vhci_device *vdev,
|
||||
struct usbip_header *pdu)
|
||||
{
|
||||
struct usbip_device *ud = &vdev->ud;
|
||||
struct urb *urb;
|
||||
|
||||
|
||||
urb = pickup_urb_and_free_priv(vdev, pdu->base.seqnum);
|
||||
|
||||
|
||||
if (!urb) {
|
||||
uerr("cannot find a urb of seqnum %u\n", pdu->base.seqnum);
|
||||
uinfo("max seqnum %d\n", atomic_read(&the_controller->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;
|
||||
|
||||
|
||||
if (dbg_flag_vhci_rx)
|
||||
usbip_dump_urb(urb);
|
||||
|
||||
|
||||
dbg_vhci_rx("now giveback urb %p\n", urb);
|
||||
|
||||
spin_lock(&the_controller->lock);
|
||||
usb_hcd_unlink_urb_from_ep(vhci_to_hcd(the_controller), urb);
|
||||
spin_unlock(&the_controller->lock);
|
||||
|
||||
usb_hcd_giveback_urb(vhci_to_hcd(the_controller), urb, urb->status);
|
||||
|
||||
|
||||
dbg_vhci_rx("Leave\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
static struct vhci_unlink *dequeue_pending_unlink(struct vhci_device *vdev,
|
||||
struct usbip_header *pdu)
|
||||
{
|
||||
struct vhci_unlink *unlink, *tmp;
|
||||
|
||||
spin_lock(&vdev->priv_lock);
|
||||
|
||||
list_for_each_entry_safe(unlink, tmp, &vdev->unlink_rx, list) {
|
||||
uinfo("unlink->seqnum %lu\n", unlink->seqnum);
|
||||
if (unlink->seqnum == pdu->base.seqnum) {
|
||||
dbg_vhci_rx("found pending unlink, %lu\n",
|
||||
unlink->seqnum);
|
||||
list_del(&unlink->list);
|
||||
|
||||
spin_unlock(&vdev->priv_lock);
|
||||
return unlink;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock(&vdev->priv_lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static void vhci_recv_ret_unlink(struct vhci_device *vdev,
|
||||
struct usbip_header *pdu)
|
||||
{
|
||||
struct vhci_unlink *unlink;
|
||||
struct urb *urb;
|
||||
|
||||
usbip_dump_header(pdu);
|
||||
|
||||
unlink = dequeue_pending_unlink(vdev, pdu);
|
||||
if (!unlink) {
|
||||
uinfo("cannot find the pending unlink %u\n", pdu->base.seqnum);
|
||||
return;
|
||||
}
|
||||
|
||||
urb = pickup_urb_and_free_priv(vdev, unlink->unlink_seqnum);
|
||||
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.
|
||||
*/
|
||||
uinfo("the urb (seqnum %d) was already given backed\n",
|
||||
pdu->base.seqnum);
|
||||
} else {
|
||||
dbg_vhci_rx("now giveback urb %p\n", urb);
|
||||
|
||||
/* If unlink is succeed, status is -ECONNRESET */
|
||||
urb->status = pdu->u.ret_unlink.status;
|
||||
uinfo("%d\n", urb->status);
|
||||
|
||||
spin_lock(&the_controller->lock);
|
||||
usb_hcd_unlink_urb_from_ep(vhci_to_hcd(the_controller), urb);
|
||||
spin_unlock(&the_controller->lock);
|
||||
|
||||
usb_hcd_giveback_urb(vhci_to_hcd(the_controller), urb,
|
||||
urb->status);
|
||||
}
|
||||
|
||||
kfree(unlink);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
|
||||
|
||||
dbg_vhci_rx("Enter\n");
|
||||
|
||||
memset(&pdu, 0, sizeof(pdu));
|
||||
|
||||
|
||||
/* 1. receive a pdu header */
|
||||
ret = usbip_xmit(0, ud->tcp_socket, (char *) &pdu, sizeof(pdu), 0);
|
||||
if (ret != sizeof(pdu)) {
|
||||
uerr("receiving pdu failed! size is %d, should be %d\n",
|
||||
ret, sizeof(pdu));
|
||||
usbip_event_add(ud, VDEV_EVENT_ERROR_TCP);
|
||||
return;
|
||||
}
|
||||
|
||||
usbip_header_correct_endian(&pdu, 0);
|
||||
|
||||
if (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:
|
||||
/* NOTREACHED */
|
||||
uerr("unknown pdu %u\n", pdu.base.command);
|
||||
usbip_dump_header(&pdu);
|
||||
usbip_event_add(ud, VDEV_EVENT_ERROR_TCP);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
void vhci_rx_loop(struct usbip_task *ut)
|
||||
{
|
||||
struct usbip_device *ud = container_of(ut, struct usbip_device, tcp_rx);
|
||||
|
||||
|
||||
while (1) {
|
||||
if (signal_pending(current)) {
|
||||
dbg_vhci_rx("signal catched!\n");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (usbip_event_happend(ud))
|
||||
break;
|
||||
|
||||
vhci_rx_pdu(ud);
|
||||
}
|
||||
}
|
||||
|
250
drivers/staging/usbip/vhci_sysfs.c
Normal file
250
drivers/staging/usbip/vhci_sysfs.c
Normal file
@ -0,0 +1,250 @@
|
||||
/*
|
||||
* 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 "usbip_common.h"
|
||||
#include "vhci.h"
|
||||
|
||||
#include <linux/in.h>
|
||||
|
||||
/* TODO: refine locking ?*/
|
||||
|
||||
/* Sysfs entry to show port status */
|
||||
static ssize_t show_status(struct device *dev, struct device_attribute *attr,
|
||||
char *out)
|
||||
{
|
||||
char *s = out;
|
||||
int i = 0;
|
||||
|
||||
if (!the_controller || !out)
|
||||
BUG();
|
||||
|
||||
spin_lock(&the_controller->lock);
|
||||
|
||||
/*
|
||||
* output example:
|
||||
* prt sta spd dev socket local_busid
|
||||
* 000 004 000 000 c5a7bb80 1-2.3
|
||||
* 001 004 000 000 d8cee980 2-3.4
|
||||
*
|
||||
* IP address can be retrieved from a socket pointer address by looking
|
||||
* up /proc/net/{tcp,tcp6}. Also, a userland program may remember a
|
||||
* port number and its peer IP address.
|
||||
*/
|
||||
out += sprintf(out, "prt sta spd bus dev socket "
|
||||
"local_busid\n");
|
||||
|
||||
for (i = 0; i < VHCI_NPORTS; i++) {
|
||||
struct vhci_device *vdev = port_to_vdev(i);
|
||||
|
||||
spin_lock(&vdev->ud.lock);
|
||||
|
||||
out += sprintf(out, "%03u %03u ", i, vdev->ud.status);
|
||||
|
||||
if (vdev->ud.status == VDEV_ST_USED) {
|
||||
out += sprintf(out, "%03u %08x ",
|
||||
vdev->speed, vdev->devid);
|
||||
out += sprintf(out, "%16p ", vdev->ud.tcp_socket);
|
||||
out += sprintf(out, "%s", vdev->udev->dev.bus_id);
|
||||
|
||||
} else
|
||||
out += sprintf(out, "000 000 000 0000000000000000 0-0");
|
||||
|
||||
out += sprintf(out, "\n");
|
||||
|
||||
spin_unlock(&vdev->ud.lock);
|
||||
}
|
||||
|
||||
spin_unlock(&the_controller->lock);
|
||||
|
||||
return out - s;
|
||||
}
|
||||
static DEVICE_ATTR(status, S_IRUGO, show_status, NULL);
|
||||
|
||||
/* Sysfs entry to shutdown a virtual connection */
|
||||
static int vhci_port_disconnect(__u32 rhport)
|
||||
{
|
||||
struct vhci_device *vdev;
|
||||
|
||||
dbg_vhci_sysfs("enter\n");
|
||||
|
||||
/* lock */
|
||||
spin_lock(&the_controller->lock);
|
||||
|
||||
vdev = port_to_vdev(rhport);
|
||||
|
||||
spin_lock(&vdev->ud.lock);
|
||||
if (vdev->ud.status == VDEV_ST_NULL) {
|
||||
uerr("not connected %d\n", vdev->ud.status);
|
||||
|
||||
/* unlock */
|
||||
spin_unlock(&vdev->ud.lock);
|
||||
spin_unlock(&the_controller->lock);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* unlock */
|
||||
spin_unlock(&vdev->ud.lock);
|
||||
spin_unlock(&the_controller->lock);
|
||||
|
||||
usbip_event_add(&vdev->ud, VDEV_EVENT_DOWN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t store_detach(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int err;
|
||||
__u32 rhport = 0;
|
||||
|
||||
sscanf(buf, "%u", &rhport);
|
||||
|
||||
/* check rhport */
|
||||
if (rhport >= VHCI_NPORTS) {
|
||||
uerr("invalid port %u\n", rhport);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = vhci_port_disconnect(rhport);
|
||||
if (err < 0)
|
||||
return -EINVAL;
|
||||
|
||||
dbg_vhci_sysfs("Leave\n");
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR(detach, S_IWUSR, NULL, store_detach);
|
||||
|
||||
/* Sysfs entry to establish a virtual connection */
|
||||
static int valid_args(__u32 rhport, enum usb_device_speed speed)
|
||||
{
|
||||
/* check rhport */
|
||||
if ((rhport < 0) || (rhport >= VHCI_NPORTS)) {
|
||||
uerr("port %u\n", rhport);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* check speed */
|
||||
switch (speed) {
|
||||
case USB_SPEED_LOW:
|
||||
case USB_SPEED_FULL:
|
||||
case USB_SPEED_HIGH:
|
||||
case USB_SPEED_VARIABLE:
|
||||
break;
|
||||
default:
|
||||
uerr("speed %d\n", speed);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* To start a new USB/IP attachment, a userland program needs to setup a TCP
|
||||
* connection and then write its socket descriptor with remote device
|
||||
* information into this sysfs file.
|
||||
*
|
||||
* A remote device is virtually attached to the root-hub port of @rhport with
|
||||
* @speed. @devid is embedded into a request to specify the remote device in a
|
||||
* server host.
|
||||
*
|
||||
* write() returns 0 on success, else negative errno.
|
||||
*/
|
||||
static ssize_t store_attach(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct vhci_device *vdev;
|
||||
struct socket *socket;
|
||||
int sockfd = 0;
|
||||
__u32 rhport = 0, devid = 0, speed = 0;
|
||||
|
||||
/*
|
||||
* @rhport: port number of vhci_hcd
|
||||
* @sockfd: socket descriptor of an established TCP connection
|
||||
* @devid: unique device identifier in a remote host
|
||||
* @speed: usb device speed in a remote host
|
||||
*/
|
||||
sscanf(buf, "%u %u %u %u", &rhport, &sockfd, &devid, &speed);
|
||||
|
||||
dbg_vhci_sysfs("rhport(%u) sockfd(%u) devid(%u) speed(%u)\n",
|
||||
rhport, sockfd, devid, speed);
|
||||
|
||||
|
||||
/* check received parameters */
|
||||
if (valid_args(rhport, speed) < 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* check sockfd */
|
||||
socket = sockfd_to_socket(sockfd);
|
||||
if (!socket)
|
||||
return -EINVAL;
|
||||
|
||||
/* now need lock until setting vdev status as used */
|
||||
|
||||
/* begin a lock */
|
||||
spin_lock(&the_controller->lock);
|
||||
|
||||
vdev = port_to_vdev(rhport);
|
||||
|
||||
spin_lock(&vdev->ud.lock);
|
||||
|
||||
if (vdev->ud.status != VDEV_ST_NULL) {
|
||||
/* end of the lock */
|
||||
spin_unlock(&vdev->ud.lock);
|
||||
spin_unlock(&the_controller->lock);
|
||||
|
||||
uerr("port %d already used\n", rhport);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
uinfo("rhport(%u) sockfd(%d) devid(%u) speed(%u)\n",
|
||||
rhport, sockfd, devid, speed);
|
||||
|
||||
vdev->devid = devid;
|
||||
vdev->speed = speed;
|
||||
vdev->ud.tcp_socket = socket;
|
||||
vdev->ud.status = VDEV_ST_NOTASSIGNED;
|
||||
|
||||
spin_unlock(&vdev->ud.lock);
|
||||
spin_unlock(&the_controller->lock);
|
||||
/* end the lock */
|
||||
|
||||
/*
|
||||
* this function will sleep, so should be out of the lock. but, it's ok
|
||||
* because we already marked vdev as being used. really?
|
||||
*/
|
||||
usbip_start_threads(&vdev->ud);
|
||||
|
||||
rh_port_connect(rhport, speed);
|
||||
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR(attach, S_IWUSR, NULL, store_attach);
|
||||
|
||||
static struct attribute *dev_attrs[] = {
|
||||
&dev_attr_status.attr,
|
||||
&dev_attr_detach.attr,
|
||||
&dev_attr_attach.attr,
|
||||
&dev_attr_usbip_debug.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
struct attribute_group dev_attr_group = {
|
||||
.attrs = dev_attrs,
|
||||
};
|
239
drivers/staging/usbip/vhci_tx.c
Normal file
239
drivers/staging/usbip/vhci_tx.c
Normal file
@ -0,0 +1,239 @@
|
||||
/*
|
||||
* 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 "usbip_common.h"
|
||||
#include "vhci.h"
|
||||
|
||||
|
||||
static void setup_cmd_submit_pdu(struct usbip_header *pdup, struct urb *urb)
|
||||
{
|
||||
struct vhci_priv *priv = ((struct vhci_priv *)urb->hcpriv);
|
||||
struct vhci_device *vdev = priv->vdev;
|
||||
|
||||
dbg_vhci_tx("URB, local devnum %u, remote devid %u\n",
|
||||
usb_pipedevice(urb->pipe), vdev->devid);
|
||||
|
||||
pdup->base.command = USBIP_CMD_SUBMIT;
|
||||
pdup->base.seqnum = priv->seqnum;
|
||||
pdup->base.devid = vdev->devid;
|
||||
if (usb_pipein(urb->pipe))
|
||||
pdup->base.direction = USBIP_DIR_IN;
|
||||
else
|
||||
pdup->base.direction = USBIP_DIR_OUT;
|
||||
pdup->base.ep = usb_pipeendpoint(urb->pipe);
|
||||
|
||||
usbip_pack_pdu(pdup, urb, USBIP_CMD_SUBMIT, 1);
|
||||
|
||||
if (urb->setup_packet)
|
||||
memcpy(pdup->u.cmd_submit.setup, urb->setup_packet, 8);
|
||||
}
|
||||
|
||||
static struct vhci_priv *dequeue_from_priv_tx(struct vhci_device *vdev)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct vhci_priv *priv, *tmp;
|
||||
|
||||
spin_lock_irqsave(&vdev->priv_lock, flags);
|
||||
|
||||
list_for_each_entry_safe(priv, tmp, &vdev->priv_tx, list) {
|
||||
list_move_tail(&priv->list, &vdev->priv_rx);
|
||||
spin_unlock_irqrestore(&vdev->priv_lock, flags);
|
||||
return priv;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&vdev->priv_lock, flags);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int vhci_send_cmd_submit(struct vhci_device *vdev)
|
||||
{
|
||||
struct vhci_priv *priv = NULL;
|
||||
|
||||
struct msghdr msg;
|
||||
struct kvec iov[3];
|
||||
size_t txsize;
|
||||
|
||||
size_t total_size = 0;
|
||||
|
||||
while ((priv = dequeue_from_priv_tx(vdev)) != NULL) {
|
||||
int ret;
|
||||
struct urb *urb = priv->urb;
|
||||
struct usbip_header pdu_header;
|
||||
void *iso_buffer = NULL;
|
||||
|
||||
txsize = 0;
|
||||
memset(&pdu_header, 0, sizeof(pdu_header));
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
memset(&iov, 0, sizeof(iov));
|
||||
|
||||
dbg_vhci_tx("setup txdata urb %p\n", urb);
|
||||
|
||||
|
||||
/* 1. setup usbip_header */
|
||||
setup_cmd_submit_pdu(&pdu_header, urb);
|
||||
usbip_header_correct_endian(&pdu_header, 1);
|
||||
|
||||
iov[0].iov_base = &pdu_header;
|
||||
iov[0].iov_len = sizeof(pdu_header);
|
||||
txsize += sizeof(pdu_header);
|
||||
|
||||
/* 2. setup transfer buffer */
|
||||
if (!usb_pipein(urb->pipe) && urb->transfer_buffer_length > 0) {
|
||||
iov[1].iov_base = urb->transfer_buffer;
|
||||
iov[1].iov_len = urb->transfer_buffer_length;
|
||||
txsize += urb->transfer_buffer_length;
|
||||
}
|
||||
|
||||
/* 3. setup iso_packet_descriptor */
|
||||
if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) {
|
||||
ssize_t len = 0;
|
||||
|
||||
iso_buffer = usbip_alloc_iso_desc_pdu(urb, &len);
|
||||
if (!iso_buffer) {
|
||||
usbip_event_add(&vdev->ud,
|
||||
SDEV_EVENT_ERROR_MALLOC);
|
||||
return -1;
|
||||
}
|
||||
|
||||
iov[2].iov_base = iso_buffer;
|
||||
iov[2].iov_len = len;
|
||||
txsize += len;
|
||||
}
|
||||
|
||||
ret = kernel_sendmsg(vdev->ud.tcp_socket, &msg, iov, 3, txsize);
|
||||
if (ret != txsize) {
|
||||
uerr("sendmsg failed!, retval %d for %zd\n", ret,
|
||||
txsize);
|
||||
kfree(iso_buffer);
|
||||
usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_TCP);
|
||||
return -1;
|
||||
}
|
||||
|
||||
kfree(iso_buffer);
|
||||
dbg_vhci_tx("send txdata\n");
|
||||
|
||||
total_size += txsize;
|
||||
}
|
||||
|
||||
return total_size;
|
||||
}
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static struct vhci_unlink *dequeue_from_unlink_tx(struct vhci_device *vdev)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct vhci_unlink *unlink, *tmp;
|
||||
|
||||
spin_lock_irqsave(&vdev->priv_lock, flags);
|
||||
|
||||
list_for_each_entry_safe(unlink, tmp, &vdev->unlink_tx, list) {
|
||||
list_move_tail(&unlink->list, &vdev->unlink_rx);
|
||||
spin_unlock_irqrestore(&vdev->priv_lock, flags);
|
||||
return unlink;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&vdev->priv_lock, flags);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int vhci_send_cmd_unlink(struct vhci_device *vdev)
|
||||
{
|
||||
struct vhci_unlink *unlink = NULL;
|
||||
|
||||
struct msghdr msg;
|
||||
struct kvec iov[3];
|
||||
size_t txsize;
|
||||
|
||||
size_t total_size = 0;
|
||||
|
||||
while ((unlink = dequeue_from_unlink_tx(vdev)) != NULL) {
|
||||
int ret;
|
||||
struct usbip_header pdu_header;
|
||||
|
||||
txsize = 0;
|
||||
memset(&pdu_header, 0, sizeof(pdu_header));
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
memset(&iov, 0, sizeof(iov));
|
||||
|
||||
dbg_vhci_tx("setup cmd unlink, %lu \n", unlink->seqnum);
|
||||
|
||||
|
||||
/* 1. setup usbip_header */
|
||||
pdu_header.base.command = USBIP_CMD_UNLINK;
|
||||
pdu_header.base.seqnum = unlink->seqnum;
|
||||
pdu_header.base.devid = vdev->devid;
|
||||
pdu_header.base.ep = 0;
|
||||
pdu_header.u.cmd_unlink.seqnum = unlink->unlink_seqnum;
|
||||
|
||||
usbip_header_correct_endian(&pdu_header, 1);
|
||||
|
||||
iov[0].iov_base = &pdu_header;
|
||||
iov[0].iov_len = sizeof(pdu_header);
|
||||
txsize += sizeof(pdu_header);
|
||||
|
||||
ret = kernel_sendmsg(vdev->ud.tcp_socket, &msg, iov, 1, txsize);
|
||||
if (ret != txsize) {
|
||||
uerr("sendmsg failed!, retval %d for %zd\n", ret,
|
||||
txsize);
|
||||
usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_TCP);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
dbg_vhci_tx("send txdata\n");
|
||||
|
||||
total_size += txsize;
|
||||
}
|
||||
|
||||
return total_size;
|
||||
}
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
void vhci_tx_loop(struct usbip_task *ut)
|
||||
{
|
||||
struct usbip_device *ud = container_of(ut, struct usbip_device, tcp_tx);
|
||||
struct vhci_device *vdev = container_of(ud, struct vhci_device, ud);
|
||||
|
||||
while (1) {
|
||||
if (signal_pending(current)) {
|
||||
uinfo("vhci_tx signal catched\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (vhci_send_cmd_submit(vdev) < 0)
|
||||
break;
|
||||
|
||||
if (vhci_send_cmd_unlink(vdev) < 0)
|
||||
break;
|
||||
|
||||
wait_event_interruptible(vdev->waitq_tx,
|
||||
(!list_empty(&vdev->priv_tx) ||
|
||||
!list_empty(&vdev->unlink_tx)));
|
||||
|
||||
dbg_vhci_tx("pending urbs ?, now wake up\n");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user