Re: [PATCH v5 2/2] usbip: Implement SG support to vhci-hcd and stub driver

From: shuah
Date: Tue Aug 13 2019 - 14:36:28 EST


On 8/8/19 10:21 AM, shuah wrote:
On 8/8/19 9:54 AM, Suwan Kim wrote:
There are bugs on vhci with usb 3.0 storage device. In USB, each SG
list entry buffer should be divisible by the bulk max packet size.
But with native SG support, this problem doesn't matter because the
SG buffer is treated as contiguous buffer. But without native SG
support, USB storage driver breaks SG list into several URBs and the
error occurs because of a buffer size of URB that cannot be divided
by the bulk max packet size. The error situation is as follows.

When USB Storage driver requests 31.5 KB data and has SG list which
has 3584 bytes buffer followed by 7 4096 bytes buffer for some
reason. USB Storage driver splits this SG list into several URBs
because VHCI doesn't support SG and sends them separately. So the
first URB buffer size is 3584 bytes. When receiving data from device,
USB 3.0 device sends data packet of 1024 bytes size because the max
packet size of BULK pipe is 1024 bytes. So device sends 4096 bytes.
But the first URB buffer has only 3584 bytes buffer size. So host
controller terminates the transfer even though there is more data to
receive. So, vhci needs to support SG transfer to prevent this error.

In this patch, vhci supports SG regardless of whether the server's
host controller supports SG or not, because stub driver splits SG
list into several URBs if the server's host controller doesn't
support SG.

To support SG, vhci_map_urb_for_dma() sets URB_DMA_MAP_SG flag in
urb->transfer_flags if URB has SG list and this flag will tell stub
driver to use SG list.

vhci sends each SG list entry to stub driver. Then, stub driver sees
the total length of the buffer and allocates SG table and pages
according to the total buffer length calling sgl_alloc(). After stub
driver receives completed URB, it again sends each SG list entry to
vhci.

If the server's host controller doesn't support SG, stub driver
breaks a single SG request into several URBs and submits them to
the server's host controller. When all the split URBs are completed,
stub driver reassembles the URBs into a single return command and
sends it to vhci.

Moreover, in the situation where vhci supports SG, but stub driver
does not, or vice versa, usbip works normally. Because there is no
protocol modification, there is no problem in communication between
server and client even if the one has a kernel without SG support.

In the case of vhci supports SG and stub driver doesn't, because
vhci sends only the total length of the buffer to stub driver as
it did before the patch applied, stub driver only needs to allocate
the required length of buffers using only kmalloc() regardless of
whether vhci supports SG or not. But stub driver has to allocate
buffer with kmalloc() as much as the total length of SG buffer which
is quite huge when vhci sends SG request, so it has overhead in
buffer allocation in this situation.

If stub driver needs to send data buffer to vhci because of IN pipe,
stub driver also sends only total length of buffer as metadata and
then sends real data as vhci does. Then vhci receive data from stub
driver and store it to the corresponding buffer of SG list entry.

And for the case of stub driver supports SG and vhci doesn't, since
the USB storage driver checks that vhci doesn't support SG and sends
the request to stub driver by splitting the SG list into multiple
URBs, stub driver allocates a buffer for each URB with kmalloc() as
it did before this patch.

* Test environment

Test uses two difference machines and two different kernel version
to make mismatch situation between the client and the server where
vhci supports SG, but stub driver does not, or vice versa. All tests
are conducted in both full SG support that both vhci and stub support
SG and half SG support that is the mismatch situation.

 - Test kernel version
ÂÂÂÂ - 5.2-rc6 with SG support
ÂÂÂÂ - 5.1.20-200.fc29.x86_64 without SG support

* SG support test

 - Test devices
ÂÂÂÂ - Super-speed storage device - SanDisk Ultra USB 3.0
ÂÂÂÂ - High-speed storage device - SMI corporation USB 2.0 flash drive

 - Test description

Test read and write operation of mass storage device that uses the
BULK transfer. In test, the client reads and writes files whose size
is over 1G and it works normally.

* Regression test

 - Test devices
ÂÂÂÂ - Super-speed device - Logitech Brio webcam
ÂÂÂÂ - High-speed device - Logitech C920 HD Pro webcam
ÂÂÂÂ - Full-speed device - Logitech bluetooth mouse
ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ - Britz BR-Orion speaker
ÂÂÂÂ - Low-speed device - Logitech wired mouse

 - Test description

Moving and click test for mouse. To test the webcam, use gnome-cheese.
To test the speaker, play music and video on the client. All works
normally.

* VUDC compatibility test

VUDC also works well with this patch. Tests are done with two USB
gadget created by CONFIGFS USB gadget. Both use the BULK pipe.

ÂÂÂÂÂÂÂÂ 1. Serial gadget
ÂÂÂÂÂÂÂÂ 2. Mass storage gadget

 - Serial gadget test

Serial gadget on the host sends and receives data using cat command
on the /dev/ttyGS<N>. The client uses minicom to communicate with
the serial gadget.

 - Mass storage gadget test

After connecting the gadget with vhci, use "dd" to test read and
write operation on the client side.

Read - dd if=/dev/sd<N> iflag=direct of=/dev/null bs=1G count=1
Write - dd if=<my file path> iflag=direct of=/dev/sd<N> bs=1G count=1


Thanks for including the detailed test description.

Signed-off-by: Suwan Kim <suwan.kim027@xxxxxxxxx>
---
v4 - v5
- Add the test description about SG support test and regression test
ÂÂ for other USB devices which have various speed.
- Fix list_del() error in stub_device_cleanup_urbs()
ÂÂ - when stub_device_cleanup_urbs() calls stub_pirv_pop() to get a
ÂÂ stub_priv, stub_priv_pop() uses list_del_init() instead of
ÂÂ list_del(). After getting the stub_priv, stub_device_cleanup_urbs()
ÂÂ calls stub_free_priv_and_urb() and it checks if list of the
ÂÂ stub_priv is empty. Because stub_priv_pop() calls list_del_init(),
ÂÂ stub_free_priv_and_urb() doesn't call list_del()

v3 - v4
- Rewrite the description about the vhci bug with USB 3.0 storage
ÂÂ device.
- Add the description about the test with VUDC.
- Fix the error message in stub_recv_cmd_unlink().

v2 - v3
- Rewrite the commit log to make it more clear.
- Add the description about the mismatch situation in commit log.
- Run chechpatch.pl and fix errors with coding style and typos.
- Fix the error path of usbip_recv_xbuff() to stop receiving data
ÂÂ after TCP error occurs.
- Consolidate the duplicated error path in usbip_recv_xbuff() and
ÂÂ vhci_send_cmd_submit().
- Undo the unnecessary changes
ÂÂ * Undo the deletion of empty line in stub_send_ret_submit()
ÂÂ * Move memset() lines in stub_send_ret_submit() to the original
ÂÂÂÂ position.

v1 - v2
- Add the logic that splits single SG request into several URBs in
ÂÂ stub driver if serverâs HC doesnât support SG.
---
 drivers/usb/usbip/stub.h | 7 +-
 drivers/usb/usbip/stub_main.c | 57 ++++++---
 drivers/usb/usbip/stub_rx.c | 204 ++++++++++++++++++++++---------
 drivers/usb/usbip/stub_tx.c | 99 +++++++++++----
 drivers/usb/usbip/usbip_common.c | 59 ++++++---
 drivers/usb/usbip/vhci_hcd.c | 15 ++-
 drivers/usb/usbip/vhci_tx.c | 63 ++++++++--
 7 files changed, 377 insertions(+), 127 deletions(-)

diff --git a/drivers/usb/usbip/stub.h b/drivers/usb/usbip/stub.h
index 35618ceb2791..d11270560c24 100644
--- a/drivers/usb/usbip/stub.h
+++ b/drivers/usb/usbip/stub.h
@@ -52,7 +52,11 @@ struct stub_priv {
ÂÂÂÂÂ unsigned long seqnum;
ÂÂÂÂÂ struct list_head list;
ÂÂÂÂÂ struct stub_device *sdev;
-ÂÂÂ struct urb *urb;
+ÂÂÂ struct urb **urbs;
+ÂÂÂ struct scatterlist *sgl;
+ÂÂÂ int num_urbs;
+ÂÂÂ int completed_urbs;
+ÂÂÂ int urb_status;
ÂÂÂÂÂ int unlinking;
 };
@@ -86,6 +90,7 @@ extern struct usb_device_driver stub_driver;
 struct bus_id_priv *get_busid_priv(const char *busid);
 void put_busid_priv(struct bus_id_priv *bid);
 int del_match_busid(char *busid);
+void stub_free_priv_and_urb(struct stub_priv *priv);
 void stub_device_cleanup_urbs(struct stub_device *sdev);
 /* stub_rx.c */
diff --git a/drivers/usb/usbip/stub_main.c b/drivers/usb/usbip/stub_main.c
index 2e4bfccd4bfc..c1c0bbc9f8b1 100644
--- a/drivers/usb/usbip/stub_main.c
+++ b/drivers/usb/usbip/stub_main.c
@@ -6,6 +6,7 @@
 #include <linux/string.h>
 #include <linux/module.h>
 #include <linux/device.h>
+#include <linux/scatterlist.h>
 #include "usbip_common.h"
 #include "stub.h"
@@ -281,13 +282,49 @@ static struct stub_priv *stub_priv_pop_from_listhead(struct list_head *listhead)
ÂÂÂÂÂ struct stub_priv *priv, *tmp;
ÂÂÂÂÂ list_for_each_entry_safe(priv, tmp, listhead, list) {
-ÂÂÂÂÂÂÂ list_del(&priv->list);
+ÂÂÂÂÂÂÂ list_del_init(&priv->list);
ÂÂÂÂÂÂÂÂÂ return priv;
ÂÂÂÂÂ }
ÂÂÂÂÂ return NULL;
 }
+void stub_free_priv_and_urb(struct stub_priv *priv)
+{
+ÂÂÂ struct urb *urb;
+ÂÂÂ int i;
+
+ÂÂÂ for (i = 0; i < priv->num_urbs; i++) {
+ÂÂÂÂÂÂÂ urb = priv->urbs[i];
+
+ÂÂÂÂÂÂÂ if (!urb)
+ÂÂÂÂÂÂÂÂÂÂÂ return;
+
+ÂÂÂÂÂÂÂ kfree(urb->setup_packet);
+ÂÂÂÂÂÂÂ urb->setup_packet = NULL;
+
+
+ÂÂÂÂÂÂÂ if (urb->transfer_buffer && !priv->sgl) {
+ÂÂÂÂÂÂÂÂÂÂÂ kfree(urb->transfer_buffer);
+ÂÂÂÂÂÂÂÂÂÂÂ urb->transfer_buffer = NULL;
+ÂÂÂÂÂÂÂ }
+
+ÂÂÂÂÂÂÂ if (urb->num_sgs) {
+ÂÂÂÂÂÂÂÂÂÂÂ sgl_free(urb->sg);
+ÂÂÂÂÂÂÂÂÂÂÂ urb->sg = NULL;
+ÂÂÂÂÂÂÂÂÂÂÂ urb->num_sgs = 0;
+ÂÂÂÂÂÂÂ }
+
+ÂÂÂÂÂÂÂ usb_free_urb(urb);
+ÂÂÂ }
+ÂÂÂ if (!list_empty(&priv->list))
+ÂÂÂÂÂÂÂ list_del(&priv->list);
+ÂÂÂ if (priv->sgl)
+ÂÂÂÂÂÂÂ sgl_free(priv->sgl);
+ÂÂÂ kfree(priv->urbs);
+ÂÂÂ kmem_cache_free(stub_priv_cache, priv);
+}
+
 static struct stub_priv *stub_priv_pop(struct stub_device *sdev)
 {
ÂÂÂÂÂ unsigned long flags;
@@ -314,25 +351,15 @@ static struct stub_priv *stub_priv_pop(struct stub_device *sdev)
 void stub_device_cleanup_urbs(struct stub_device *sdev)
 {
ÂÂÂÂÂ struct stub_priv *priv;
-ÂÂÂ struct urb *urb;
+ÂÂÂ int i;
ÂÂÂÂÂ dev_dbg(&sdev->udev->dev, "Stub device cleaning up urbs\n");
ÂÂÂÂÂ while ((priv = stub_priv_pop(sdev))) {
-ÂÂÂÂÂÂÂ urb = priv->urb;
-ÂÂÂÂÂÂÂ dev_dbg(&sdev->udev->dev, "free urb seqnum %lu\n",
-ÂÂÂÂÂÂÂÂÂÂÂ priv->seqnum);
-ÂÂÂÂÂÂÂ usb_kill_urb(urb);
-
-ÂÂÂÂÂÂÂ kmem_cache_free(stub_priv_cache, priv);
+ÂÂÂÂÂÂÂ for (i = 0; i < priv->num_urbs; i++)
+ÂÂÂÂÂÂÂÂÂÂÂ usb_kill_urb(priv->urbs[i]);
-ÂÂÂÂÂÂÂ kfree(urb->transfer_buffer);
-ÂÂÂÂÂÂÂ urb->transfer_buffer = NULL;
-
-ÂÂÂÂÂÂÂ kfree(urb->setup_packet);
-ÂÂÂÂÂÂÂ urb->setup_packet = NULL;
-
-ÂÂÂÂÂÂÂ usb_free_urb(urb);
+ÂÂÂÂÂÂÂ stub_free_priv_and_urb(priv);
ÂÂÂÂÂ }
 }
diff --git a/drivers/usb/usbip/stub_rx.c b/drivers/usb/usbip/stub_rx.c
index b0a855acafa3..66edfeea68fe 100644
--- a/drivers/usb/usbip/stub_rx.c
+++ b/drivers/usb/usbip/stub_rx.c
@@ -7,6 +7,7 @@
 #include <linux/kthread.h>
 #include <linux/usb.h>
 #include <linux/usb/hcd.h>
+#include <linux/scatterlist.h>
 #include "usbip_common.h"
 #include "stub.h"
@@ -201,7 +202,7 @@ static void tweak_special_requests(struct urb *urb)
 static int stub_recv_cmd_unlink(struct stub_device *sdev,
ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ struct usbip_header *pdu)
 {
-ÂÂÂ int ret;
+ÂÂÂ int ret, i;
ÂÂÂÂÂ unsigned long flags;
ÂÂÂÂÂ struct stub_priv *priv;
@@ -246,12 +247,14 @@ static int stub_recv_cmd_unlink(struct stub_device *sdev,
ÂÂÂÂÂÂÂÂÂÂ * so a driver in a client host will know the failure
ÂÂÂÂÂÂÂÂÂÂ * of the unlink request ?
ÂÂÂÂÂÂÂÂÂÂ */
-ÂÂÂÂÂÂÂ ret = usb_unlink_urb(priv->urb);
-ÂÂÂÂÂÂÂ if (ret != -EINPROGRESS)
-ÂÂÂÂÂÂÂÂÂÂÂ dev_err(&priv->urb->dev->dev,
-ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ "failed to unlink a urb # %lu, ret %d\n",
-ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ priv->seqnum, ret);
-
+ÂÂÂÂÂÂÂ for (i = priv->completed_urbs; i < priv->num_urbs; i++) {
+ÂÂÂÂÂÂÂÂÂÂÂ ret = usb_unlink_urb(priv->urbs[i]);
+ÂÂÂÂÂÂÂÂÂÂÂ if (ret != -EINPROGRESS)
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ dev_err(&priv->urbs[i]->dev->dev,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ "failed to unlink %d/%d urb of seqnum %lu, ret %d\n",
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ i + 1, priv->num_urbs,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ priv->seqnum, ret);
+ÂÂÂÂÂÂÂ }
ÂÂÂÂÂÂÂÂÂ return 0;
ÂÂÂÂÂ }
@@ -433,14 +436,36 @@ static void masking_bogus_flags(struct urb *urb)
ÂÂÂÂÂ urb->transfer_flags &= allowed;
 }
+static int stub_recv_xbuff(struct usbip_device *ud, struct stub_priv *priv)
+{
+ÂÂÂ int ret;
+ÂÂÂ int i;
+
+ÂÂÂ for (i = 0; i < priv->num_urbs; i++) {
+ÂÂÂÂÂÂÂ ret = usbip_recv_xbuff(ud, priv->urbs[i]);
+ÂÂÂÂÂÂÂ if (ret < 0)
+ÂÂÂÂÂÂÂÂÂÂÂ break;
+ÂÂÂ }
+
+ÂÂÂ return ret;
+}
+
 static void stub_recv_cmd_submit(struct stub_device *sdev,
ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ struct usbip_header *pdu)
 {
-ÂÂÂ int ret;
ÂÂÂÂÂ struct stub_priv *priv;
ÂÂÂÂÂ struct usbip_device *ud = &sdev->ud;
ÂÂÂÂÂ struct usb_device *udev = sdev->udev;
+ÂÂÂ struct scatterlist *sgl = NULL, *sg;
+ÂÂÂ void *buffer = NULL;
+ÂÂÂ unsigned long long buf_len;
+ÂÂÂ int nents;
+ÂÂÂ int num_urbs = 1;
ÂÂÂÂÂ int pipe = get_pipe(sdev, pdu);
+ÂÂÂ int use_sg = pdu->u.cmd_submit.transfer_flags & URB_DMA_MAP_SG;
+ÂÂÂ int support_sg = 1;
+ÂÂÂ int np = 0;
+ÂÂÂ int ret, i;
ÂÂÂÂÂ if (pipe == -1)
ÂÂÂÂÂÂÂÂÂ return;
@@ -449,76 +474,139 @@ static void stub_recv_cmd_submit(struct stub_device *sdev,
ÂÂÂÂÂ if (!priv)
ÂÂÂÂÂÂÂÂÂ return;
-ÂÂÂ /* setup a urb */
-ÂÂÂ if (usb_pipeisoc(pipe))
-ÂÂÂÂÂÂÂ priv->urb = usb_alloc_urb(pdu->u.cmd_submit.number_of_packets,
-ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ GFP_KERNEL);
-ÂÂÂ else
-ÂÂÂÂÂÂÂ priv->urb = usb_alloc_urb(0, GFP_KERNEL);
+ÂÂÂ buf_len = (unsigned long long)pdu->u.cmd_submit.transfer_buffer_length;
-ÂÂÂ if (!priv->urb) {
-ÂÂÂÂÂÂÂ usbip_event_add(ud, SDEV_EVENT_ERROR_MALLOC);
-ÂÂÂÂÂÂÂ return;
+ÂÂÂ /* allocate urb transfer buffer, if needed */
+ÂÂÂ if (buf_len) {
+ÂÂÂÂÂÂÂ if (use_sg) {
+ÂÂÂÂÂÂÂÂÂÂÂ sgl = sgl_alloc(buf_len, GFP_KERNEL, &nents);
+ÂÂÂÂÂÂÂÂÂÂÂ if (!sgl)
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ goto err_malloc;
+ÂÂÂÂÂÂÂ } else {
+ÂÂÂÂÂÂÂÂÂÂÂ buffer = kzalloc(buf_len, GFP_KERNEL);
+ÂÂÂÂÂÂÂÂÂÂÂ if (!buffer)
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ goto err_malloc;
+ÂÂÂÂÂÂÂ }
ÂÂÂÂÂ }
-ÂÂÂ /* allocate urb transfer buffer, if needed */
-ÂÂÂ if (pdu->u.cmd_submit.transfer_buffer_length > 0) {
-ÂÂÂÂÂÂÂ priv->urb->transfer_buffer =
-ÂÂÂÂÂÂÂÂÂÂÂ kzalloc(pdu->u.cmd_submit.transfer_buffer_length,
-ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ GFP_KERNEL);
-ÂÂÂÂÂÂÂ if (!priv->urb->transfer_buffer) {
+ÂÂÂ /* Check if the server's HCD supports SG */
+ÂÂÂ if (use_sg && !udev->bus->sg_tablesize) {
+ÂÂÂÂÂÂÂ /*
+ÂÂÂÂÂÂÂÂ * If the server's HCD doesn't support SG, break a single SG
+ÂÂÂÂÂÂÂÂ * request into several URBs and map each SG list entry to
+ÂÂÂÂÂÂÂÂ * corresponding URB buffer. The previously allocated SG
+ÂÂÂÂÂÂÂÂ * list is stored in priv->sgl (If the server's HCD support SG,
+ÂÂÂÂÂÂÂÂ * SG list is stored only in urb->sg) and it is used as an
+ÂÂÂÂÂÂÂÂ * indicator that the server split single SG request into
+ÂÂÂÂÂÂÂÂ * several URBs. Later, priv->sgl is used by stub_complete() and
+ÂÂÂÂÂÂÂÂ * stub_send_ret_submit() to reassemble the divied URBs.
+ÂÂÂÂÂÂÂÂ */
+ÂÂÂÂÂÂÂ support_sg = 0;
+ÂÂÂÂÂÂÂ num_urbs = nents;
+ÂÂÂÂÂÂÂ priv->completed_urbs = 0;
+ÂÂÂÂÂÂÂ pdu->u.cmd_submit.transfer_flags &= ~URB_DMA_MAP_SG;
+ÂÂÂ }
+
+ÂÂÂ /* allocate urb array */
+ÂÂÂ priv->num_urbs = num_urbs;
+ÂÂÂ priv->urbs = kmalloc_array(num_urbs, sizeof(*priv->urbs), GFP_KERNEL);
+ÂÂÂ if (!priv->urbs)
+ÂÂÂÂÂÂÂ goto err_urbs;
+
+ÂÂÂ /* setup a urb */
+ÂÂÂ if (support_sg) {
+ÂÂÂÂÂÂÂ if (usb_pipeisoc(pipe))
+ÂÂÂÂÂÂÂÂÂÂÂ np = pdu->u.cmd_submit.number_of_packets;
+
+ÂÂÂÂÂÂÂ priv->urbs[0] = usb_alloc_urb(np, GFP_KERNEL);
+ÂÂÂÂÂÂÂ if (!priv->urbs[0])
+ÂÂÂÂÂÂÂÂÂÂÂ goto err_urb;
+
+ÂÂÂÂÂÂÂ if (buf_len) {
+ÂÂÂÂÂÂÂÂÂÂÂ if (use_sg) {
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ priv->urbs[0]->sg = sgl;
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ priv->urbs[0]->num_sgs = nents;
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ priv->urbs[0]->transfer_buffer = NULL;
+ÂÂÂÂÂÂÂÂÂÂÂ } else {
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ priv->urbs[0]->transfer_buffer = buffer;
+ÂÂÂÂÂÂÂÂÂÂÂ }
+ÂÂÂÂÂÂÂ }
+
+ÂÂÂÂÂÂÂ /* copy urb setup packet */
+ÂÂÂÂÂÂÂ priv->urbs[0]->setup_packet = kmemdup(&pdu->u.cmd_submit.setup,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ 8, GFP_KERNEL);
+ÂÂÂÂÂÂÂ if (!priv->urbs[0]->setup_packet) {
ÂÂÂÂÂÂÂÂÂÂÂÂÂ usbip_event_add(ud, SDEV_EVENT_ERROR_MALLOC);
ÂÂÂÂÂÂÂÂÂÂÂÂÂ return;
ÂÂÂÂÂÂÂÂÂ }
-ÂÂÂ }
-ÂÂÂ /* copy urb setup packet */
-ÂÂÂ priv->urb->setup_packet = kmemdup(&pdu->u.cmd_submit.setup, 8,
-ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ GFP_KERNEL);
-ÂÂÂ if (!priv->urb->setup_packet) {
-ÂÂÂÂÂÂÂ dev_err(&udev->dev, "allocate setup_packet\n");
-ÂÂÂÂÂÂÂ usbip_event_add(ud, SDEV_EVENT_ERROR_MALLOC);
-ÂÂÂÂÂÂÂ return;
+ÂÂÂÂÂÂÂ usbip_pack_pdu(pdu, priv->urbs[0], USBIP_CMD_SUBMIT, 0);
+ÂÂÂ } else {
+ÂÂÂÂÂÂÂ for_each_sg(sgl, sg, nents, i) {
+ÂÂÂÂÂÂÂÂÂÂÂ priv->urbs[i] = usb_alloc_urb(0, GFP_KERNEL);
+ÂÂÂÂÂÂÂÂÂÂÂ /* The URBs which is previously allocated will be freed
+ÂÂÂÂÂÂÂÂÂÂÂÂ * in stub_device_cleanup_urbs() if error occurs.
+ÂÂÂÂÂÂÂÂÂÂÂÂ */
+ÂÂÂÂÂÂÂÂÂÂÂ if (!priv->urbs[i])
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ goto err_urb;
+
+ÂÂÂÂÂÂÂÂÂÂÂ usbip_pack_pdu(pdu, priv->urbs[i], USBIP_CMD_SUBMIT, 0);
+ÂÂÂÂÂÂÂÂÂÂÂ priv->urbs[i]->transfer_buffer = sg_virt(sg);
+ÂÂÂÂÂÂÂÂÂÂÂ priv->urbs[i]->transfer_buffer_length = sg->length;
+ÂÂÂÂÂÂÂ }
+ÂÂÂÂÂÂÂ priv->sgl = sgl;
ÂÂÂÂÂ }
-ÂÂÂ /* set other members from the base header of pdu */
-ÂÂÂ priv->urb->contextÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ = (void *) priv;
-ÂÂÂ priv->urb->devÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ = udev;
-ÂÂÂ priv->urb->pipeÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ = pipe;
-ÂÂÂ priv->urb->completeÂÂÂÂÂÂÂÂÂÂÂÂÂÂ = stub_complete;
+ÂÂÂ for (i = 0; i < num_urbs; i++) {
+ÂÂÂÂÂÂÂ /* set other members from the base header of pdu */
+ÂÂÂÂÂÂÂ priv->urbs[i]->context = (void *) priv;
+ÂÂÂÂÂÂÂ priv->urbs[i]->dev = udev;
+ÂÂÂÂÂÂÂ priv->urbs[i]->pipe = pipe;
+ÂÂÂÂÂÂÂ priv->urbs[i]->complete = stub_complete;
-ÂÂÂ usbip_pack_pdu(pdu, priv->urb, USBIP_CMD_SUBMIT, 0);
+ÂÂÂÂÂÂÂ /* no need to submit an intercepted request, but harmless? */
+ÂÂÂÂÂÂÂ tweak_special_requests(priv->urbs[i]);
+ÂÂÂÂÂÂÂ masking_bogus_flags(priv->urbs[i]);
+ÂÂÂ }
-ÂÂÂ if (usbip_recv_xbuff(ud, priv->urb) < 0)
+ÂÂÂ if (stub_recv_xbuff(ud, priv) < 0)
ÂÂÂÂÂÂÂÂÂ return;
-ÂÂÂ if (usbip_recv_iso(ud, priv->urb) < 0)
+ÂÂÂ if (usbip_recv_iso(ud, priv->urbs[0]) < 0)
ÂÂÂÂÂÂÂÂÂ return;
-ÂÂÂ /* no need to submit an intercepted request, but harmless? */
-ÂÂÂ tweak_special_requests(priv->urb);
-
-ÂÂÂ masking_bogus_flags(priv->urb);
ÂÂÂÂÂ /* urb is now ready to submit */
-ÂÂÂ ret = usb_submit_urb(priv->urb, GFP_KERNEL);
-
-ÂÂÂ if (ret == 0)
-ÂÂÂÂÂÂÂ usbip_dbg_stub_rx("submit urb ok, seqnum %u\n",
-ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ pdu->base.seqnum);
-ÂÂÂ else {
-ÂÂÂÂÂÂÂ dev_err(&udev->dev, "submit_urb error, %d\n", ret);
-ÂÂÂÂÂÂÂ usbip_dump_header(pdu);
-ÂÂÂÂÂÂÂ usbip_dump_urb(priv->urb);
-
-ÂÂÂÂÂÂÂ /*
-ÂÂÂÂÂÂÂÂ * Pessimistic.
-ÂÂÂÂÂÂÂÂ * This connection will be discarded.
-ÂÂÂÂÂÂÂÂ */
-ÂÂÂÂÂÂÂ usbip_event_add(ud, SDEV_EVENT_ERROR_SUBMIT);
+ÂÂÂ for (i = 0; i < priv->num_urbs; i++) {
+ÂÂÂÂÂÂÂ ret = usb_submit_urb(priv->urbs[i], GFP_KERNEL);
+
+ÂÂÂÂÂÂÂ if (ret == 0)
+ÂÂÂÂÂÂÂÂÂÂÂ usbip_dbg_stub_rx("submit urb ok, seqnum %u\n",
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ pdu->base.seqnum);
+ÂÂÂÂÂÂÂ else {
+ÂÂÂÂÂÂÂÂÂÂÂ dev_err(&udev->dev, "submit_urb error, %d\n", ret);
+ÂÂÂÂÂÂÂÂÂÂÂ usbip_dump_header(pdu);
+ÂÂÂÂÂÂÂÂÂÂÂ usbip_dump_urb(priv->urbs[i]);
+
+ÂÂÂÂÂÂÂÂÂÂÂ /*
+ÂÂÂÂÂÂÂÂÂÂÂÂ * Pessimistic.
+ÂÂÂÂÂÂÂÂÂÂÂÂ * This connection will be discarded.
+ÂÂÂÂÂÂÂÂÂÂÂÂ */
+ÂÂÂÂÂÂÂÂÂÂÂ usbip_event_add(ud, SDEV_EVENT_ERROR_SUBMIT);
+ÂÂÂÂÂÂÂÂÂÂÂ break;
+ÂÂÂÂÂÂÂ }
ÂÂÂÂÂ }
ÂÂÂÂÂ usbip_dbg_stub_rx("Leave\n");
+ÂÂÂ return;
+
+err_urb:
+ÂÂÂ kfree(priv->urbs);
+err_urbs:
+ÂÂÂ kfree(buffer);
+ÂÂÂ sgl_free(sgl);
+err_malloc:
+ÂÂÂ usbip_event_add(ud, SDEV_EVENT_ERROR_MALLOC);
 }
 /* recv a pdu */
diff --git a/drivers/usb/usbip/stub_tx.c b/drivers/usb/usbip/stub_tx.c
index f0ec41a50cbc..36010a82b359 100644
--- a/drivers/usb/usbip/stub_tx.c
+++ b/drivers/usb/usbip/stub_tx.c
@@ -5,25 +5,11 @@
 #include <linux/kthread.h>
 #include <linux/socket.h>
+#include <linux/scatterlist.h>
 #include "usbip_common.h"
 #include "stub.h"
-static void stub_free_priv_and_urb(struct stub_priv *priv)
-{
-ÂÂÂ struct urb *urb = priv->urb;
-
-ÂÂÂ kfree(urb->setup_packet);
-ÂÂÂ urb->setup_packet = NULL;
-
-ÂÂÂ kfree(urb->transfer_buffer);
-ÂÂÂ urb->transfer_buffer = NULL;
-
-ÂÂÂ list_del(&priv->list);
-ÂÂÂ kmem_cache_free(stub_priv_cache, priv);
-ÂÂÂ usb_free_urb(urb);
-}
-
 /* be in spin_lock_irqsave(&sdev->priv_lock, flags) */
 void stub_enqueue_ret_unlink(struct stub_device *sdev, __u32 seqnum,
ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ __u32 status)
@@ -85,6 +71,22 @@ void stub_complete(struct urb *urb)
ÂÂÂÂÂÂÂÂÂ break;
ÂÂÂÂÂ }
+ÂÂÂ /*
+ÂÂÂÂ * If the server breaks single SG request into the several URBs, the
+ÂÂÂÂ * URBs must be reassembled before sending completed URB to the vhci.
+ÂÂÂÂ * Don't wake up the tx thread until all the URBs are completed.
+ÂÂÂÂ */
+ÂÂÂ if (priv->sgl) {
+ÂÂÂÂÂÂÂ priv->completed_urbs++;
+
+ÂÂÂÂÂÂÂ /* Only save the first error status */
+ÂÂÂÂÂÂÂ if (urb->status && !priv->urb_status)
+ÂÂÂÂÂÂÂÂÂÂÂ priv->urb_status = urb->status;
+
+ÂÂÂÂÂÂÂ if (priv->completed_urbs < priv->num_urbs)
+ÂÂÂÂÂÂÂÂÂÂÂ return;
+ÂÂÂ }
+
ÂÂÂÂÂ /* link a urb to the queue of tx. */
ÂÂÂÂÂ spin_lock_irqsave(&sdev->priv_lock, flags);
ÂÂÂÂÂ if (sdev->ud.tcp_socket == NULL) {
@@ -156,18 +158,22 @@ static int stub_send_ret_submit(struct stub_device *sdev)
ÂÂÂÂÂ size_t total_size = 0;
ÂÂÂÂÂ while ((priv = dequeue_from_priv_tx(sdev)) != NULL) {
-ÂÂÂÂÂÂÂ int ret;
-ÂÂÂÂÂÂÂ struct urb *urb = priv->urb;
+ÂÂÂÂÂÂÂ struct urb *urb = priv->urbs[0];
ÂÂÂÂÂÂÂÂÂ struct usbip_header pdu_header;
ÂÂÂÂÂÂÂÂÂ struct usbip_iso_packet_descriptor *iso_buffer = NULL;
ÂÂÂÂÂÂÂÂÂ struct kvec *iov = NULL;
+ÂÂÂÂÂÂÂ struct scatterlist *sg;
+ÂÂÂÂÂÂÂ u32 actual_length = 0;
ÂÂÂÂÂÂÂÂÂ int iovnum = 0;
+ÂÂÂÂÂÂÂ int ret;
+ÂÂÂÂÂÂÂ int i;
ÂÂÂÂÂÂÂÂÂ txsize = 0;
ÂÂÂÂÂÂÂÂÂ memset(&pdu_header, 0, sizeof(pdu_header));
ÂÂÂÂÂÂÂÂÂ memset(&msg, 0, sizeof(msg));
-ÂÂÂÂÂÂÂ if (urb->actual_length > 0 && !urb->transfer_buffer) {
+ÂÂÂÂÂÂÂ if (urb->actual_length > 0 && !urb->transfer_buffer &&
+ÂÂÂÂÂÂÂÂÂÂ !urb->num_sgs) {
ÂÂÂÂÂÂÂÂÂÂÂÂÂ dev_err(&sdev->udev->dev,
ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ "urb: actual_length %d transfer_buffer null\n",
ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ urb->actual_length);
@@ -176,6 +182,11 @@ static int stub_send_ret_submit(struct stub_device *sdev)
ÂÂÂÂÂÂÂÂÂ if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS)
ÂÂÂÂÂÂÂÂÂÂÂÂÂ iovnum = 2 + urb->number_of_packets;
+ÂÂÂÂÂÂÂ else if (usb_pipein(urb->pipe) && urb->actual_length > 0 &&
+ÂÂÂÂÂÂÂÂÂÂÂ urb->num_sgs)
+ÂÂÂÂÂÂÂÂÂÂÂ iovnum = 1 + urb->num_sgs;
+ÂÂÂÂÂÂÂ else if (usb_pipein(urb->pipe) && priv->sgl)
+ÂÂÂÂÂÂÂÂÂÂÂ iovnum = 1 + priv->num_urbs;
ÂÂÂÂÂÂÂÂÂ else
ÂÂÂÂÂÂÂÂÂÂÂÂÂ iovnum = 2;
@@ -192,6 +203,15 @@ static int stub_send_ret_submit(struct stub_device *sdev)
ÂÂÂÂÂÂÂÂÂ setup_ret_submit_pdu(&pdu_header, urb);
ÂÂÂÂÂÂÂÂÂ usbip_dbg_stub_tx("setup txdata seqnum: %d\n",
ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ pdu_header.base.seqnum);
+
+ÂÂÂÂÂÂÂ if (priv->sgl) {
+ÂÂÂÂÂÂÂÂÂÂÂ for (i = 0; i < priv->num_urbs; i++)
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ actual_length += priv->urbs[i]->actual_length;
+
+ÂÂÂÂÂÂÂÂÂÂÂ pdu_header.u.ret_submit.status = priv->urb_status;
+ÂÂÂÂÂÂÂÂÂÂÂ pdu_header.u.ret_submit.actual_length = actual_length;
+ÂÂÂÂÂÂÂ }
+
ÂÂÂÂÂÂÂÂÂ usbip_header_correct_endian(&pdu_header, 1);
ÂÂÂÂÂÂÂÂÂ iov[iovnum].iov_base = &pdu_header;
@@ -200,12 +220,47 @@ static int stub_send_ret_submit(struct stub_device *sdev)
ÂÂÂÂÂÂÂÂÂ txsize += sizeof(pdu_header);
ÂÂÂÂÂÂÂÂÂ /* 2. setup transfer buffer */
-ÂÂÂÂÂÂÂ if (usb_pipein(urb->pipe) &&
+ÂÂÂÂÂÂÂ if (usb_pipein(urb->pipe) && priv->sgl) {
+ÂÂÂÂÂÂÂÂÂÂÂ /* If the server split a single SG request into several
+ÂÂÂÂÂÂÂÂÂÂÂÂ * URBs because the server's HCD doesn't support SG,
+ÂÂÂÂÂÂÂÂÂÂÂÂ * reassemble the split URB buffers into a single
+ÂÂÂÂÂÂÂÂÂÂÂÂ * return command.
+ÂÂÂÂÂÂÂÂÂÂÂÂ */
+ÂÂÂÂÂÂÂÂÂÂÂ for (i = 0; i < priv->num_urbs; i++) {
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ iov[iovnum].iov_base =
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ priv->urbs[i]->transfer_buffer;
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ iov[iovnum].iov_len =
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ priv->urbs[i]->actual_length;
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ iovnum++;
+ÂÂÂÂÂÂÂÂÂÂÂ }
+ÂÂÂÂÂÂÂÂÂÂÂ txsize += actual_length;
+ÂÂÂÂÂÂÂ } else if (usb_pipein(urb->pipe) &&
ÂÂÂÂÂÂÂÂÂÂÂÂÂ usb_pipetype(urb->pipe) != PIPE_ISOCHRONOUS &&
ÂÂÂÂÂÂÂÂÂÂÂÂÂ urb->actual_length > 0) {
-ÂÂÂÂÂÂÂÂÂÂÂ iov[iovnum].iov_base = urb->transfer_buffer;
- iov[iovnum].iov_len = urb->actual_length;
-ÂÂÂÂÂÂÂÂÂÂÂ iovnum++;
+ÂÂÂÂÂÂÂÂÂÂÂ if (urb->num_sgs) {
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ unsigned int copy = urb->actual_length;
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ int size;
+
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ for_each_sg(urb->sg, sg, urb->num_sgs, i) {
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ if (copy == 0)
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ break;
+
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ if (copy < sg->length)
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ size = copy;
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ else
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ size = sg->length;
+
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ iov[iovnum].iov_base = sg_virt(sg);
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ iov[iovnum].iov_len = size;
+
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ iovnum++;
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ copy -= size;
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ }
+ÂÂÂÂÂÂÂÂÂÂÂ } else {
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ iov[iovnum].iov_base = urb->transfer_buffer;
+ iov[iovnum].iov_len = urb->actual_length;
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ iovnum++;
+ÂÂÂÂÂÂÂÂÂÂÂ }
ÂÂÂÂÂÂÂÂÂÂÂÂÂ txsize += urb->actual_length;
ÂÂÂÂÂÂÂÂÂ } else if (usb_pipein(urb->pipe) &&
ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) {
diff --git a/drivers/usb/usbip/usbip_common.c b/drivers/usb/usbip/usbip_common.c
index 45da3e01c7b0..6532d68e8808 100644
--- a/drivers/usb/usbip/usbip_common.c
+++ b/drivers/usb/usbip/usbip_common.c
@@ -680,8 +680,12 @@ EXPORT_SYMBOL_GPL(usbip_pad_iso);
 /* some members of urb must be substituted before. */
 int usbip_recv_xbuff(struct usbip_device *ud, struct urb *urb)
 {
-ÂÂÂ int ret;
+ÂÂÂ struct scatterlist *sg;
+ÂÂÂ int ret = 0;
+ÂÂÂ int recv;
ÂÂÂÂÂ int size;
+ÂÂÂ int copy;
+ÂÂÂ int i;
ÂÂÂÂÂ if (ud->side == USBIP_STUB || ud->side == USBIP_VUDC) {
ÂÂÂÂÂÂÂÂÂ /* the direction of urb must be OUT. */
@@ -701,29 +705,48 @@ int usbip_recv_xbuff(struct usbip_device *ud, struct urb *urb)
ÂÂÂÂÂ if (!(size > 0))
ÂÂÂÂÂÂÂÂÂ return 0;
-ÂÂÂ if (size > urb->transfer_buffer_length) {
+ÂÂÂ if (size > urb->transfer_buffer_length)
ÂÂÂÂÂÂÂÂÂ /* should not happen, probably malicious packet */
-ÂÂÂÂÂÂÂ if (ud->side == USBIP_STUB) {
-ÂÂÂÂÂÂÂÂÂÂÂ usbip_event_add(ud, SDEV_EVENT_ERROR_TCP);
-ÂÂÂÂÂÂÂÂÂÂÂ return 0;
-ÂÂÂÂÂÂÂ } else {
-ÂÂÂÂÂÂÂÂÂÂÂ usbip_event_add(ud, VDEV_EVENT_ERROR_TCP);
-ÂÂÂÂÂÂÂÂÂÂÂ return -EPIPE;
-ÂÂÂÂÂÂÂ }
-ÂÂÂ }
+ÂÂÂÂÂÂÂ goto error;
-ÂÂÂ ret = usbip_recv(ud->tcp_socket, urb->transfer_buffer, size);
-ÂÂÂ if (ret != size) {
-ÂÂÂÂÂÂÂ dev_err(&urb->dev->dev, "recv xbuf, %d\n", ret);
-ÂÂÂÂÂÂÂ if (ud->side == USBIP_STUB || ud->side == USBIP_VUDC) {
-ÂÂÂÂÂÂÂÂÂÂÂ usbip_event_add(ud, SDEV_EVENT_ERROR_TCP);
-ÂÂÂÂÂÂÂ } else {
-ÂÂÂÂÂÂÂÂÂÂÂ usbip_event_add(ud, VDEV_EVENT_ERROR_TCP);
-ÂÂÂÂÂÂÂÂÂÂÂ return -EPIPE;
+ÂÂÂ if (urb->num_sgs) {
+ÂÂÂÂÂÂÂ copy = size;
+ÂÂÂÂÂÂÂ for_each_sg(urb->sg, sg, urb->num_sgs, i) {
+ÂÂÂÂÂÂÂÂÂÂÂ int recv_size;
+
+ÂÂÂÂÂÂÂÂÂÂÂ if (copy < sg->length)
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ recv_size = copy;
+ÂÂÂÂÂÂÂÂÂÂÂ else
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ recv_size = sg->length;
+
+ÂÂÂÂÂÂÂÂÂÂÂ recv = usbip_recv(ud->tcp_socket, sg_virt(sg),
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ recv_size);
+
+ÂÂÂÂÂÂÂÂÂÂÂ if (recv != recv_size)
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ goto error;
+
+ÂÂÂÂÂÂÂÂÂÂÂ copy -= recv;
+ÂÂÂÂÂÂÂÂÂÂÂ ret += recv;
ÂÂÂÂÂÂÂÂÂ }
+
+ÂÂÂÂÂÂÂ if (ret != size)
+ÂÂÂÂÂÂÂÂÂÂÂ goto error;
+ÂÂÂ } else {
+ÂÂÂÂÂÂÂ ret = usbip_recv(ud->tcp_socket, urb->transfer_buffer, size);
+ÂÂÂÂÂÂÂ if (ret != size)
+ÂÂÂÂÂÂÂÂÂÂÂ goto error;
ÂÂÂÂÂ }
ÂÂÂÂÂ return ret;
+
+error:
+ÂÂÂ dev_err(&urb->dev->dev, "recv xbuf, %d\n", ret);
+ÂÂÂ if (ud->side == USBIP_STUB || ud->side == USBIP_VUDC)
+ÂÂÂÂÂÂÂ usbip_event_add(ud, SDEV_EVENT_ERROR_TCP);
+ÂÂÂ else
+ÂÂÂÂÂÂÂ usbip_event_add(ud, VDEV_EVENT_ERROR_TCP);
+
+ÂÂÂ return -EPIPE;
 }
 EXPORT_SYMBOL_GPL(usbip_recv_xbuff);
diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c
index ea82b932a2f9..e64ab50cbe2b 100644
--- a/drivers/usb/usbip/vhci_hcd.c
+++ b/drivers/usb/usbip/vhci_hcd.c
@@ -697,7 +697,8 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
ÂÂÂÂÂ }
ÂÂÂÂÂ vdev = &vhci_hcd->vdev[portnum-1];
-ÂÂÂ if (!urb->transfer_buffer && urb->transfer_buffer_length) {
+ÂÂÂ if (!urb->transfer_buffer && !urb->num_sgs &&
+ÂÂÂÂÂÂÂÂ urb->transfer_buffer_length) {
ÂÂÂÂÂÂÂÂÂ dev_dbg(dev, "Null URB transfer buffer\n");
ÂÂÂÂÂÂÂÂÂ return -EINVAL;
ÂÂÂÂÂ }
@@ -1143,6 +1144,15 @@ static int vhci_setup(struct usb_hcd *hcd)
ÂÂÂÂÂÂÂÂÂ hcd->speed = HCD_USB3;
ÂÂÂÂÂÂÂÂÂ hcd->self.root_hub->speed = USB_SPEED_SUPER;
ÂÂÂÂÂ }
+
+ÂÂÂ /*
+ÂÂÂÂ * Support SG.
+ÂÂÂÂ * sg_tablesize is an arbitrary value to alleviate memory pressure
+ÂÂÂÂ * on the host.
+ÂÂÂÂ */
+ÂÂÂ hcd->self.sg_tablesize = 32;
+ÂÂÂ hcd->self.no_sg_constraint = 1;
+
ÂÂÂÂÂ return 0;
 }
@@ -1296,6 +1306,9 @@ static int vhci_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
ÂÂÂÂÂÂÂÂÂ return -EINVAL;
ÂÂÂÂÂ }
+ÂÂÂ if (urb->num_sgs)
+ÂÂÂÂÂÂÂ urb->transfer_flags |= URB_DMA_MAP_SG;
+
ÂÂÂÂÂ return 0;
 }
diff --git a/drivers/usb/usbip/vhci_tx.c b/drivers/usb/usbip/vhci_tx.c
index 2fa26d0578d7..865eb1276b6c 100644
--- a/drivers/usb/usbip/vhci_tx.c
+++ b/drivers/usb/usbip/vhci_tx.c
@@ -5,6 +5,7 @@
 #include <linux/kthread.h>
 #include <linux/slab.h>
+#include <linux/scatterlist.h>
 #include "usbip_common.h"
 #include "vhci.h"
@@ -50,19 +51,23 @@ static struct vhci_priv *dequeue_from_priv_tx(struct vhci_device *vdev)
 static int vhci_send_cmd_submit(struct vhci_device *vdev)
 {
+ÂÂÂ struct usbip_iso_packet_descriptor *iso_buffer = NULL;
ÂÂÂÂÂ struct vhci_priv *priv = NULL;
+ÂÂÂ struct scatterlist *sg;
ÂÂÂÂÂ struct msghdr msg;
-ÂÂÂ struct kvec iov[3];
+ÂÂÂ struct kvec *iov;
ÂÂÂÂÂ size_t txsize;
ÂÂÂÂÂ size_t total_size = 0;
+ÂÂÂ int iovnum;
+ÂÂÂ int err = -ENOMEM;
+ÂÂÂ int i;
ÂÂÂÂÂ while ((priv = dequeue_from_priv_tx(vdev)) != NULL) {
ÂÂÂÂÂÂÂÂÂ int ret;
ÂÂÂÂÂÂÂÂÂ struct urb *urb = priv->urb;
ÂÂÂÂÂÂÂÂÂ struct usbip_header pdu_header;
-ÂÂÂÂÂÂÂ struct usbip_iso_packet_descriptor *iso_buffer = NULL;
ÂÂÂÂÂÂÂÂÂ txsize = 0;
ÂÂÂÂÂÂÂÂÂ memset(&pdu_header, 0, sizeof(pdu_header));
@@ -72,18 +77,42 @@ static int vhci_send_cmd_submit(struct vhci_device *vdev)
ÂÂÂÂÂÂÂÂÂ usbip_dbg_vhci_tx("setup txdata urb seqnum %lu\n",
ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ priv->seqnum);
+ÂÂÂÂÂÂÂ if (urb->num_sgs && usb_pipeout(urb->pipe))
+ÂÂÂÂÂÂÂÂÂÂÂ iovnum = 2 + urb->num_sgs;
+ÂÂÂÂÂÂÂ else
+ÂÂÂÂÂÂÂÂÂÂÂ iovnum = 3;
+
+ÂÂÂÂÂÂÂ iov = kcalloc(iovnum, sizeof(*iov), GFP_KERNEL);
+ÂÂÂÂÂÂÂ if (!iov) {
+ÂÂÂÂÂÂÂÂÂÂÂ usbip_event_add(&vdev->ud, SDEV_EVENT_ERROR_MALLOC);
+ÂÂÂÂÂÂÂÂÂÂÂ return -ENOMEM;
+ÂÂÂÂÂÂÂ }
+
ÂÂÂÂÂÂÂÂÂ /* 1. setup usbip_header */
ÂÂÂÂÂÂÂÂÂ setup_cmd_submit_pdu(&pdu_header, urb);
ÂÂÂÂÂÂÂÂÂ usbip_header_correct_endian(&pdu_header, 1);
+ÂÂÂÂÂÂÂ iovnum = 0;
-ÂÂÂÂÂÂÂ iov[0].iov_base = &pdu_header;
- iov[0].iov_len = sizeof(pdu_header);
+ÂÂÂÂÂÂÂ iov[iovnum].iov_base = &pdu_header;
+ iov[iovnum].iov_len = sizeof(pdu_header);
ÂÂÂÂÂÂÂÂÂ txsize += sizeof(pdu_header);
+ÂÂÂÂÂÂÂ iovnum++;
ÂÂÂÂÂÂÂÂÂ /* 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;
+ÂÂÂÂÂÂÂÂÂÂÂ if (urb->num_sgs &&
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ !usb_endpoint_xfer_isoc(&urb->ep->desc)) {
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ for_each_sg(urb->sg, sg, urb->num_sgs, i) {
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ iov[iovnum].iov_base = sg_virt(sg);
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ iov[iovnum].iov_len = sg->length;
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ iovnum++;
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ }
+ÂÂÂÂÂÂÂÂÂÂÂ } else {
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ iov[iovnum].iov_base = urb->transfer_buffer;
+ iov[iovnum].iov_len =
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ urb->transfer_buffer_length;
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ iovnum++;
+ÂÂÂÂÂÂÂÂÂÂÂ }
ÂÂÂÂÂÂÂÂÂÂÂÂÂ txsize += urb->transfer_buffer_length;
ÂÂÂÂÂÂÂÂÂ }
@@ -95,23 +124,26 @@ static int vhci_send_cmd_submit(struct vhci_device *vdev)
ÂÂÂÂÂÂÂÂÂÂÂÂÂ if (!iso_buffer) {
ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ usbip_event_add(&vdev->ud,
ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ SDEV_EVENT_ERROR_MALLOC);
-ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ return -1;
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ goto err_iso_buffer;
ÂÂÂÂÂÂÂÂÂÂÂÂÂ }
-ÂÂÂÂÂÂÂÂÂÂÂ iov[2].iov_base = iso_buffer;
- iov[2].iov_len = len;
+ÂÂÂÂÂÂÂÂÂÂÂ iov[iovnum].iov_base = iso_buffer;
+ iov[iovnum].iov_len = len;
+ÂÂÂÂÂÂÂÂÂÂÂ iovnum++;
ÂÂÂÂÂÂÂÂÂÂÂÂÂ txsize += len;
ÂÂÂÂÂÂÂÂÂ }
-ÂÂÂÂÂÂÂ ret = kernel_sendmsg(vdev->ud.tcp_socket, &msg, iov, 3, txsize);
+ÂÂÂÂÂÂÂ ret = kernel_sendmsg(vdev->ud.tcp_socket, &msg, iov, iovnum,
+ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ txsize);
ÂÂÂÂÂÂÂÂÂ if (ret != txsize) {
ÂÂÂÂÂÂÂÂÂÂÂÂÂ pr_err("sendmsg failed!, ret=%d for %zd\n", ret,
ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂ txsize);
-ÂÂÂÂÂÂÂÂÂÂÂ kfree(iso_buffer);
ÂÂÂÂÂÂÂÂÂÂÂÂÂ usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_TCP);
-ÂÂÂÂÂÂÂÂÂÂÂ return -1;
+ÂÂÂÂÂÂÂÂÂÂÂ err = -EPIPE;
+ÂÂÂÂÂÂÂÂÂÂÂ goto err_tx;
ÂÂÂÂÂÂÂÂÂ }
+ÂÂÂÂÂÂÂ kfree(iov);
ÂÂÂÂÂÂÂÂÂ kfree(iso_buffer);
ÂÂÂÂÂÂÂÂÂ usbip_dbg_vhci_tx("send txdata\n");
@@ -119,6 +151,13 @@ static int vhci_send_cmd_submit(struct vhci_device *vdev)
ÂÂÂÂÂ }
ÂÂÂÂÂ return total_size;
+
+err_tx:
+ÂÂÂ kfree(iso_buffer);
+err_iso_buffer:
+ÂÂÂ kfree(iov);
+
+ÂÂÂ return err;
 }
 static struct vhci_unlink *dequeue_from_unlink_tx(struct vhci_device *vdev)


Thanks for doing this work.

Reviewed-by: Shuah Khan <skhan@xxxxxxxxxxxxxxxxxxx>


Greg!

Please pick this up as well. This is good from usbip
perspective.

Acked-by: Shuah Khan <skhan@xxxxxxxxxxxxxxxxxxx>

thanks,
-- Shuah