View Issue Details

IDProjectCategoryView StatusLast Update
0001608channel: elrepo/el9kmod-omn-vhci-hcdpublic2026-10-02 10:05
Reportertqhoang Assigned Totqhoang  
PrioritynormalSeveritymajorReproducibilityN/A
Status assignedResolutionopen 
Platformx86_64OSRHELOS Version9.8
Summary0001608: VHCI-HCD for Omnissa Horizon 8 Agent for Linux
DescriptionNote: The excerpt below was via email.
--
I started looking at the Omnissa Horizon 8 Agent for Linux.
[snipped]
This is a product we need to get working on RHEL 9.

It requires VHCI-HCD and V4L2Loopback with patches and kernel modules
build on each system for our use case. DKMS implementation is left to
the user. There are weekly kernel updates from Red Hat, so automating
kernel module rebuild is a must have.

I see that ElRepo provides both solutions, but neither can be used
because of the patching required by Omnissa.

I am wondering if there is any way of making the ElRepo provided
packages working for the Omnissa Horizon 8 Agent and if that would
possibly break other use cases of those packages.

Have you seen any requests for compatibility of the ElRepo provided
packages for the Horizon Agent?

Here are a couple of links to the Omnissa documentation for Omnissa
Horizon 8 2603, the latest Extended Service Branch (ESB) with Horizon
Agent for 64-bit Linux version 8.18.0:

System Requirements for Horizon Agent for Linux - VHCI Driver for USB
Redirection:
https://docs.omnissa.com/Desktops-and-Applications-in-Horizon-V2603/SystemRequirementsforHorizonAgentforLinux

(vhci-hcd-1.15.tar.gz from
https://sourceforge.net/projects/usb-vhci/files/linux%20kernel%20module/)

Install the V4L2Loopback Driver on a Linux Machine
https://docs.omnissa.com/Desktops-and-Applications-in-Horizon-V2603/InstalltheV4L2LoopbackDriveronaLinuxMachine

v0.12.5 Source code from https://github.com/umlaeute/v4l2loopback/tags

(Omnissa documentation requires patching for this one, but the patch
does not seem to apply.)

I would like to hear your take on this situation.

I hope to be able to connect with the Omnissa people in the next
couple of weeks to raise all my concerns about the packaging of this
solution and the fact that I feel I cannot deploy it on all the
systems I support in the current state.
Being able to use the packages from ElRepo would definitely help.
TagsNo tags attached.
Attached Files
vhci.patch (65,724 bytes)   
diff -uprN vhci-hcd-1.15.orig/Makefile vhci-hcd-1.15/Makefile
--- vhci-hcd-1.15.orig/Makefile	2015-06-27 17:09:14.000000000 +0530
+++ vhci-hcd-1.15/Makefile	2026-03-16 15:14:49.297102913 +0530
@@ -20,6 +20,7 @@ PWD = $(shell pwd)
 INSTALL_DIR = $(INSTALL_PREFIX)/lib/modules/$(KVERSION)
 DEST = $(INSTALL_DIR)/kernel/$(MDIR)
 KSRC = $(KDIR)
+EXTRA_CFLAGS += -DKVERSION_VERSION=$(KVERSION_VERSION) -DKVERSION_PATCHLEVEL=$(KVERSION_PATCHLEVEL)
 
 CONF_H = conf/usb-vhci.config.h
 
@@ -30,9 +31,10 @@ DIST_DIRS = patch test
 DIST_FILES = AUTHORS ChangeLog COPYING INSTALL Makefile NEWS README TODO usb-vhci-hcd.c usb-vhci-iocifc.c usb-vhci-hcd.h usb-vhci.h usb-vhci-dump-urb.c patch/Kconfig.patch test/Makefile test/test.c
 
 obj-m := $(OBJS)
+ccflags-y += $(EXTRA_CFLAGS)
 
 default: $(CONF_H)
-	make -C $(KDIR) SUBDIRS=$(PWD) PWD=$(PWD) BUILD_PREFIX=$(BUILD_PREFIX) KDIR=$(KDIR) KVERSION=$(KVERSION) modules
+	make -C $(KDIR) M=$(PWD) PWD=$(PWD) BUILD_PREFIX=$(BUILD_PREFIX) KDIR=$(KDIR) KVERSION=$(KVERSION) modules
 .PHONY: default
 .SUFFIXES:
 
@@ -88,7 +90,7 @@ srcdox: clean-srcdox
 $(CONF_H):
 	$(MAKE) testconfig
 
-TESTMAKE = make -C $(KDIR) SUBDIRS=$(PWD)/test PWD=$(PWD)/test BUILD_PREFIX=$(BUILD_PREFIX) KDIR=$(KDIR) KVERSION=$(KVERSION) EXTRA_CFLAGS='-Wno-unused $(EXTRA_CFLAGS) $1' modules
+TESTMAKE = make -C $(KDIR) M=$(PWD)/test PWD=$(PWD)/test BUILD_PREFIX=$(BUILD_PREFIX) KDIR=$(KDIR) KVERSION=$(KVERSION) EXTRA_CFLAGS='-Wno-unused $(EXTRA_CFLAGS) $1' modules
 
 testcc: clean-test
 	$(call TESTMAKE)
@@ -130,6 +132,12 @@ testconfig: testcc
 	else \
 		echo "#define NO_HAS_TT_FLAG" >>$(CONF_H); \
 	fi
+	$(MAKE) clean-test
+	if $(call TESTMAKE,-DTEST_CLASS_OWNER) >/dev/null 2>&1; then \
+		echo "//#define NO_CLASS_OWNER" >>$(CONF_H); \
+	else \
+		echo "#define NO_CLASS_OWNER" >>$(CONF_H); \
+	fi
 	echo "// end of file" >>$(CONF_H)
 .PHONY: testconfig
 
diff -uprN vhci-hcd-1.15/test/test.c vhci-hcd-1.15.patch/test/test.c
--- vhci-hcd-1.15/test/test.c	2013-06-21 08:02:38.000000000 +0800
+++ vhci-hcd-1.15.patch/test/test.c	2024-05-07 13:08:53.934717791 +0800
@@ -68,6 +68,13 @@ static struct usb_hcd testhcd = {
 };
 #endif
 
+static struct class testclass = {
+#ifdef TEST_CLASS_OWNER
+	.owner = THIS_MODULE,
+#endif
+	.name = "test"
+};
+
 static int __init init(void)
 {
 	if(usb_disabled()) return -ENODEV;
diff -uprN vhci-hcd-1.15/usb-vhci-dump-urb.c vhci-hcd-1.15.patch/usb-vhci-dump-urb.c
--- vhci-hcd-1.15/usb-vhci-dump-urb.c	2013-06-21 08:02:38.000000000 +0800
+++ vhci-hcd-1.15.patch/usb-vhci-dump-urb.c	2024-05-07 11:10:42.629267925 +0800
@@ -148,3 +148,15 @@ static void dump_urb(struct urb *urb)
 		printk("\n");
 	}
 }
+
+static void dump_urbs(struct urb **urbs, int num_urbs)
+{
+	int i = 0;
+	if(!debug_output) return;
+	for(i = 0; i < num_urbs; i++)
+	{
+		struct urb *urb = urbs[i];
+		vhci_printk(KERN_DEBUG, "dump urbs[%d]:\n", i);
+		dump_urb(urb);
+	}
+}
diff -uprN vhci-hcd-1.15/usb-vhci.h vhci-hcd-1.15.patch/usb-vhci.h
--- vhci-hcd-1.15/usb-vhci.h	2013-06-21 08:02:38.000000000 +0800
+++ vhci-hcd-1.15.patch/usb-vhci.h	2024-05-07 11:10:42.630267938 +0800
@@ -52,6 +52,15 @@
 //#define USB_PORT_STAT_TEST          0x0800
 //#define USB_PORT_STAT_INDICATOR     0x1000
 
+/*
+ * Additions to wPortStatus bit field from USB 3.0
+ * See USB 3.0 spec Table 10-10
+ */
+#define USB_PORT_STAT_LINK_STATE        0x01e0
+#define USB_SS_PORT_STAT_POWER          0x0200
+#define USB_SS_PORT_STAT_SPEED          0x1c00
+#define USB_PORT_STAT_SPEED_5GBPS       0x0000
+
 // wPortChange bit field
 // See USB 2.0 spec Table 11-22
 #define USB_PORT_STAT_C_CONNECTION  0x0001
@@ -60,6 +69,14 @@
 #define USB_PORT_STAT_C_OVERCURRENT 0x0008
 #define USB_PORT_STAT_C_RESET       0x0010
 
+/*
+ * USB 3.0 wPortChange bit fields
+ * See USB 3.0 spec Table 10-11
+ */
+#define USB_PORT_STAT_C_BH_RESET        0x0020
+#define USB_PORT_STAT_C_LINK_STATE      0x0040
+#define USB_PORT_STAT_C_CONFIG_ERROR    0x0080
+
 #endif
 
 // structure for the USB_VHCI_HCD_IOCREGISTER ioctl
diff -uprN vhci-hcd-1.15/usb-vhci-hcd.c vhci-hcd-1.15.patch/usb-vhci-hcd.c
--- vhci-hcd-1.15/usb-vhci-hcd.c	2013-06-21 08:02:38.000000000 +0800
+++ vhci-hcd-1.15.patch/usb-vhci-hcd.c	2024-05-07 11:10:42.630267938 +0800
@@ -73,15 +73,28 @@
 #	define trace_function(dev) do {} while(0)
 #endif
 
+#if KVERSION_VERSION < 3
+#	define HCD_IRQ -1
+#elif KVERSION_VERSION == 3 && KVERSION_PATCHLEVEL < 4
+#	define HCD_IRQ -1
+#else
+#	define HCD_IRQ 0
+#endif
+
 static const char driver_name[] = DRIVER_NAME;
 static const char driver_desc[] = DRIVER_DESC;
 #ifdef DEBUG
 static unsigned int debug_output = 0;
 #endif
 
+static bool is_support_usb3 = true;
+module_param_named(is_support_usb3, is_support_usb3, bool, S_IRUGO);
+MODULE_PARM_DESC(is_support_usb3, "true to support usb3.0 devices, false to support usb2.0 only");
+
 MODULE_DESCRIPTION(DRIVER_DESC " driver");
 MODULE_AUTHOR("Michael Singer <michael@a-singer.de>");
 MODULE_LICENSE("GPL");
+MODULE_VERSION("1.15.0");
 
 static inline const char *vhci_dev_name(struct device *dev)
 {
@@ -115,6 +128,8 @@ int usb_vhci_dev_id(struct usb_vhci_devi
 }
 EXPORT_SYMBOL_GPL(usb_vhci_dev_id);
 
+// TODO: There are two USB bus now, but this function only returns USB2.0 busnum
+// It's okay because USB3.0 busnum equal to USB2.0 busnum + 1
 int usb_vhci_dev_busnum(struct usb_vhci_device *vdev)
 {
 	return vhcidev_to_usbhcd(vdev)->self.busnum;
@@ -124,12 +139,15 @@ EXPORT_SYMBOL_GPL(usb_vhci_dev_busnum);
 void usb_vhci_maybe_set_status(struct usb_vhci_urb_priv *urbp, int status)
 {
 #ifdef OLD_GIVEBACK_MECH
-	struct urb *const urb = urbp->urb;
+	int i;
 	unsigned long flags;
-	spin_lock_irqsave(&urb->lock, flags);
-	if(urb->status == -EINPROGRESS)
-		urb->status = status;
-	spin_unlock_irqrestore(&urb->lock, flags);
+	for (i = 0; i < urbp->num_urbs; ++i) {
+		struct urb *const urb = urbp->urbs[i];
+		spin_lock_irqsave(&urb->lock, flags);
+		if(urb->status == -EINPROGRESS)
+			urb->status = status;
+		spin_unlock_irqrestore(&urb->lock, flags);
+	}
 #else
 	(void)atomic_cmpxchg(&urbp->status, -EINPROGRESS, status);
 #endif
@@ -139,7 +157,7 @@ EXPORT_SYMBOL_GPL(usb_vhci_maybe_set_sta
 #ifdef DEBUG
 #include "usb-vhci-dump-urb.c"
 #else
-static inline void dump_urb(struct urb *urb) {/* do nothing */}
+static inline void dump_urbs(struct urb **urbs, int num_urbs) {/* do nothing */}
 #endif
 
 // caller has vhc->lock
@@ -147,7 +165,7 @@ static inline void dump_urb(struct urb *
 static void vhci_port_update(struct usb_vhci_hcd *vhc, u8 port)
 {
 	struct usb_vhci_device *vdev = vhcihcd_to_vhcidev(vhc);
-	vhc->port_update |= 1 << port;
+	vhc->port_update |= (u64)1 << port;
 	vdev->ifc->wakeup(vdev);
 }
 
@@ -157,34 +175,57 @@ void usb_vhci_urb_giveback(struct usb_vh
 {
 	struct device *dev;
 	struct usb_hcd *hcd;
-	struct urb *const urb = urbp->urb;
-	struct usb_device *const udev = urb->dev;
+	struct usb_device *const udev = urbp->urbs[0]->dev;
+	int i;
 #ifndef OLD_GIVEBACK_MECH
 	int status;
 #endif
-	hcd = vhcihcd_to_usbhcd(vhc);
+
+	if(urbp->urbs[0]->dev->speed >= USB_SPEED_SUPER)
+		hcd = vhc->ss_hcd;
+	else
+		hcd = vhc->hs_hcd;
+
 	dev = vhcihcd_to_dev(vhc);
 	trace_function(dev);
 #ifndef OLD_GIVEBACK_MECH
 	status = atomic_read(&urbp->status);
 #endif
-	urb->hcpriv = NULL;
 	list_del(&urbp->urbp_list);
 #ifndef OLD_GIVEBACK_MECH
-	usb_hcd_unlink_urb_from_ep(hcd, urb);
+	for(i = 0; i < urbp->num_urbs; i++)
+	{
+		urbp->urbs[i]->hcpriv = NULL;
+		usb_hcd_unlink_urb_from_ep(hcd, urbp->urbs[i]);
+	}
 #endif
 	spin_unlock(&vhc->lock);
-	kfree(urbp);
-	dump_urb(urb);
+	dump_urbs(urbp->urbs, urbp->num_urbs);
+#ifdef DEBUG
+	if(debug_output)
+	{
+		for(i = 0; i < urbp->num_urbs; i++)
+			dev_dbg(dev, "giveback urbs[%d]: actual_length %d, transfer_buffer_length %d\n", i, urbp->urbs[i]->actual_length, urbp->urbs[i]->transfer_buffer_length);
+	}
+#endif
+
 #ifdef OLD_GIVEBACK_MECH
-	usb_hcd_giveback_urb(hcd, urb);
+	for (i = 0; i < urbp->num_urbs; i++)
+	{
+		usb_hcd_giveback_urb(hcd, urbp->urbs[i]);
+		usb_put_dev(udev);
+	}
 #else
 #	ifdef DEBUG
 	if(debug_output) vhci_printk(KERN_DEBUG, "usb_vhci_urb_giveback: status=%d(%s)\n", status, get_status_str(status));
 #	endif
-	usb_hcd_giveback_urb(hcd, urb, status);
+	for(i = 0; i < urbp->num_urbs; i++)
+	{
+		usb_hcd_giveback_urb(hcd, urbp->urbs[i], status);
+		usb_put_dev(udev);
+	}
 #endif
-	usb_put_dev(udev);
+	kfree(urbp);
 	spin_lock(&vhc->lock);
 }
 EXPORT_SYMBOL_GPL(usb_vhci_urb_giveback);
@@ -200,9 +241,12 @@ static int vhci_urb_enqueue(struct usb_h
 	struct usb_vhci_urb_priv *urbp;
 	struct usb_vhci_device *vdev;
 	unsigned long flags;
+	struct usb_sg_request *io;
+	int i = 0;
 #ifndef OLD_GIVEBACK_MECH
 	int retval;
 #endif
+	io = (struct usb_sg_request *)urb->context;
 
 	vhc = usbhcd_to_vhcihcd(hcd);
 	dev = vhcihcd_to_dev(vhc);
@@ -210,30 +254,56 @@ static int vhci_urb_enqueue(struct usb_h
 
 	trace_function(dev);
 
+#ifdef DEBUG
+	if(debug_output) vhci_printk(KERN_INFO, "%s: urb %p, entries %d, transfer_buffer_length %d, type %d, is_dir_in %d, urb->status = %d(%s)\n",
+			__FUNCTION__, urb, io->entries, urb->transfer_buffer_length, usb_pipetype(urb->pipe), usb_pipein(urb->pipe), urb->status,get_status_str(urb->status));
+#endif
+
 	if(unlikely(!urb->transfer_buffer && urb->transfer_buffer_length))
 		return -EINVAL;
 
-	urbp = kzalloc(sizeof *urbp, mem_flags);
-	if(unlikely(!urbp))
-		return -ENOMEM;
-	urbp->urb = urb;
-	atomic_set(&urbp->status, urb->status);
+	if(io->entries > 0 && io->entries <= 30 && usb_pipebulk(urb->pipe) && urb != io->urbs[io->entries - 1])
+	{
+		return 0;
+	}
 
-	vhci_dbg("vhci_urb_enqueue: urb->status = %d(%s)",urb->status,get_status_str(urb->status));
+	if (io->entries > 0 && io->entries <= 30 && usb_pipebulk(urb->pipe))
+	{
+		urbp = kzalloc(sizeof *urbp + sizeof urb * io->entries, mem_flags);
+		if(unlikely(!urbp)) return -ENOMEM;
 
-	spin_lock_irqsave(&vhc->lock, flags);
-#ifndef OLD_GIVEBACK_MECH
-	retval = usb_hcd_link_urb_to_ep(hcd, urb);
-	if(unlikely(retval))
+		for(i = 0; i < io->entries; ++i)
+		{
+			urbp->urbs[i] = io->urbs[i];
+		}
+		urbp->num_urbs = io->entries;
+	}
+	else
 	{
-		kfree(urbp);
-		spin_unlock_irqrestore(&vhc->lock, flags);
-		return retval;
+		urbp = kzalloc(sizeof *urbp + sizeof urb, mem_flags);
+		if(unlikely(!urbp)) return -ENOMEM;
+
+		urbp->urbs[0] = urb;
+		urbp->num_urbs = 1;
 	}
+	atomic_set(&urbp->status, urb->status);
+
+	spin_lock_irqsave(&vhc->lock, flags);
+	for(i = 0; i < urbp->num_urbs; ++i)
+	{
+#ifndef OLD_GIVEBACK_MECH
+		retval = usb_hcd_link_urb_to_ep(hcd, urbp->urbs[i]);
+		if(unlikely(retval))
+		{
+			kfree(urbp);
+			spin_unlock_irqrestore(&vhc->lock, flags);
+			return retval;
+    	}
 #endif
-	usb_get_dev(urb->dev);
+		usb_get_dev(urbp->urbs[i]->dev);
+		urbp->urbs[i]->hcpriv = urbp;
+	}
 	list_add_tail(&urbp->urbp_list, &vhc->urbp_list_inbox);
-	urb->hcpriv = urbp;
 	spin_unlock_irqrestore(&vhc->lock, flags);
 	vdev->ifc->wakeup(vdev);
 	return 0;
@@ -253,12 +323,16 @@ static int vhci_urb_dequeue(struct usb_h
 #ifndef OLD_GIVEBACK_MECH
 	int retval;
 #endif
+	struct usb_sg_request *io = (struct usb_sg_request *)urb->context;
 
 	vhc = usbhcd_to_vhcihcd(hcd);
 	dev = vhcihcd_to_dev(vhc);
 	vdev = vhcihcd_to_vhcidev(vhc);
 
 	trace_function(dev);
+#ifdef DEBUG
+	vhci_dbg("vhci_urb_dequeue: entries %d, urb %p, urb->status = %d(%s)",io->entries,urb,urb->status,get_status_str(urb->status));
+#endif
 
 	spin_lock_irqsave(&vhc->lock, flags);
 #ifndef OLD_GIVEBACK_MECH
@@ -269,11 +343,15 @@ static int vhci_urb_dequeue(struct usb_h
 		return retval;
 	}
 #endif
+	if(usb_pipebulk(urb->pipe) && io->entries > 0 && io->entries <= 30 && io->urbs[io->entries - 1] != urb)
+	{
+		goto exit;
+	}
 
 	// search the queue of unprocessed urbs (inbox)
 	list_for_each_entry(entry, &vhc->urbp_list_inbox, urbp_list)
 	{
-		if(entry->urb == urb)
+		if(entry->urbs == ((struct usb_vhci_urb_priv *)(urb->hcpriv))->urbs)
 		{
 			urbp = entry;
 			break;
@@ -281,14 +359,20 @@ static int vhci_urb_dequeue(struct usb_h
 	}
 
 	// if found in inbox
-	if(urbp)
+	if(urbp) {
+                // If the vhci have been disabled
+                if (vhc->disabled == true) {
+                        // give back urb with shutdown status
+                        usb_vhci_maybe_set_status(urbp, -ESHUTDOWN);
+                }
 		usb_vhci_urb_giveback(vhc, urbp);
+        }
 	else // if not found...
 	{
 		// ...then check if the urb is on a vacation through user space
 		list_for_each_entry(entry, &vhc->urbp_list_fetched, urbp_list)
 		{
-			if(entry->urb == urb)
+			if(entry->urbs == ((struct usb_vhci_urb_priv *)(urb->hcpriv))->urbs)
 			{
 				// move it into the cancel list
 				list_move_tail(&entry->urbp_list, &vhc->urbp_list_cancel);
@@ -298,6 +382,7 @@ static int vhci_urb_dequeue(struct usb_h
 		}
 	}
 
+exit:
 	spin_unlock_irqrestore(&vhc->lock, flags);
 	return 0;
 }
@@ -309,21 +394,56 @@ static void vhci_timer(unsigned long _vh
 }
 */
 
+static void get_vhci_params(struct usb_hcd *hcd, struct usb_vhci_hcd *vhc, // IN
+							u8 *port_count, struct usb_vhci_port **ports, u8 *index, u8 *offset //OUT
+							)
+{
+	u8 count, idx, os;
+	struct usb_vhci_port *ps;
+
+	if(hcd->speed >= HCD_USB3)
+	{
+		count = vhc->usb3_port_count;
+		ps = vhc->ports + vhc->usb2_port_count;
+		os = vhc->usb2_port_count;
+		idx = 1;
+	}
+	else
+	{
+		count = vhc->usb2_port_count;
+		ps = vhc->ports;
+		os = 0;
+		idx = 0;
+	}
+
+	if(port_count) *port_count = count;
+	if(ports)      *ports = ps;
+	if(index)      *index = idx;
+	if(offset)     *offset = os;
+}
+
 static int vhci_hub_status(struct usb_hcd *hcd, char *buf)
 {
 	struct usb_vhci_hcd *vhc;
 	struct device *dev;
 	unsigned long flags;
 	u8 port;
-	int retval = 0;
+	int changed = 0;
 	int idx, rel_bit, abs_bit;
+	struct usb_vhci_port *ports;
+	u8 port_count;
+	u8 index;
+	int retval;
 
 	vhc = usbhcd_to_vhcihcd(hcd);
 	dev = vhcihcd_to_dev(vhc);
 
 	trace_function(dev);
 
-	memset(buf, 0, 1 + vhc->port_count / 8);
+	get_vhci_params(hcd, vhc, &port_count, &ports, &index, NULL);
+
+	retval = 1 + port_count / 8;
+	memset(buf, 0, retval);
 
 	spin_lock_irqsave(&vhc->lock, flags);
 	if(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))
@@ -332,52 +452,101 @@ static int vhci_hub_status(struct usb_hc
 		return 0;
 	}
 
-	for(port = 0; port < vhc->port_count; port++)
+	for(port = 0; port < port_count; port++)
 	{
-		if(vhc->ports[port].port_change)
+		if(ports[port].port_change)
 		{
 			abs_bit = port + 1;
 			idx     = abs_bit / (sizeof *buf * 8);
 			rel_bit = abs_bit % (sizeof *buf * 8);
 			buf[idx] |= (1 << rel_bit);
-			retval = 1;
+			changed = 1;
 		}
 #ifdef DEBUG
-		if(debug_output) dev_dbg(dev, "port %d status 0x%04x has changes at 0x%04x\n", (int)(port + 1), (int)vhc->ports[port].port_status, (int)vhc->ports[port].port_change);
+		if(debug_output) dev_dbg(dev, "USB%d.0: port %d status 0x%04x has changes at 0x%04x\n", (hcd->speed == HCD_USB3)? 3 : 2, (int)(port + 1), (int)ports[port].port_status, (int)ports[port].port_change);
 #endif
 	}
 
-	if(vhc->rh_state == USB_VHCI_RH_SUSPENDED)
+	if((vhc->rh_state[index] == USB_VHCI_RH_SUSPENDED) && changed)
 		usb_hcd_resume_root_hub(hcd);
 
 	spin_unlock_irqrestore(&vhc->lock, flags);
-	return retval;
+	return changed ? retval : 0;
 }
 
+/* usb 3.0 root hub device descriptor */
+static struct {
+        struct usb_bos_descriptor bos;
+        struct usb_ss_cap_descriptor ss_cap;
+} __packed usb3_bos_desc = {
+
+        .bos = {
+                .bLength                = USB_DT_BOS_SIZE,
+                .bDescriptorType        = USB_DT_BOS,
+                .wTotalLength           = cpu_to_le16(sizeof(usb3_bos_desc)),
+                .bNumDeviceCaps         = 1,
+        },
+        .ss_cap = {
+                .bLength                = USB_DT_USB_SS_CAP_SIZE,
+                .bDescriptorType        = USB_DT_DEVICE_CAPABILITY,
+                .bDevCapabilityType     = USB_SS_CAP_TYPE,
+                .wSpeedSupported        = cpu_to_le16(USB_5GBPS_OPERATION),
+                .bFunctionalitySupport  = ilog2(USB_5GBPS_OPERATION),
+        },
+};
+
 // caller has vhc->lock
 // called in vhci_hub_control only
+static inline void ss_hub_descriptor(const struct usb_vhci_hcd *vhc, char *buf, u16 len)
+{
+	struct usb_hub_descriptor desc;
+	int portArrLen = vhc->usb3_port_count / 8 + 1; // length of one port bit-array in bytes
+	u16 l = USB_DT_SS_HUB_SIZE + 2 * portArrLen; // length of our hub descriptor
+	memset(&desc, 0, USB_DT_SS_HUB_SIZE);
+
+	if(likely(len > USB_DT_SS_HUB_SIZE))
+	{
+		if(unlikely(len < l)) l = len;
+		if(likely(l > USB_DT_SS_HUB_SIZE))
+		{
+		    memset(buf + USB_DT_SS_HUB_SIZE, 0, l - USB_DT_SS_HUB_SIZE);
+		    if(likely(l > USB_DT_SS_HUB_SIZE + portArrLen))
+			memset(buf + USB_DT_SS_HUB_SIZE + portArrLen, 0xff, l - (USB_DT_SS_HUB_SIZE + portArrLen));
+		}
+	}
+	else l = len;
+
+	desc.bDescLength = l;
+	desc.bDescriptorType = 0x2a;
+	desc.bNbrPorts = vhc->usb3_port_count;
+	desc.wHubCharacteristics = __constant_cpu_to_le16(0x0009); // Per port power and overcurrent
+	desc.u.ss.bHubHdrDecLat = 0x04; /* Worst case: 0.4 micro sec*/
+	desc.u.ss.DeviceRemovable = 0xffff;
+	memcpy(buf, &desc, l);
+}
+
 static inline void hub_descriptor(const struct usb_vhci_hcd *vhc, char *buf, u16 len)
 {
 	struct usb_hub_descriptor desc;
-	int portArrLen = vhc->port_count / 8 + 1; // length of one port bit-array in bytes
+	int portArrLen = vhc->usb2_port_count / 8 + 1; // length of one port bit-array in bytes
 	u16 l = USB_DT_HUB_NONVAR_SIZE + 2 * portArrLen; // length of our hub descriptor
 	memset(&desc, 0, USB_DT_HUB_NONVAR_SIZE);
 
 	if(likely(len > USB_DT_HUB_NONVAR_SIZE))
 	{
-		if(unlikely(len < l)) l = len;
-		if(likely(l > USB_DT_HUB_NONVAR_SIZE))
-		{
-			memset(buf + USB_DT_HUB_NONVAR_SIZE, 0, l - USB_DT_HUB_NONVAR_SIZE);
-			if(likely(l > USB_DT_HUB_NONVAR_SIZE + portArrLen))
-				memset(buf + USB_DT_HUB_NONVAR_SIZE + portArrLen, 0xff, l - (USB_DT_HUB_NONVAR_SIZE + portArrLen));
-		}
+	if(unlikely(len < l)) l = len;
+	if(likely(l > USB_DT_HUB_NONVAR_SIZE))
+	{
+	    memset(buf + USB_DT_HUB_NONVAR_SIZE, 0, l - USB_DT_HUB_NONVAR_SIZE);
+	    if(likely(l > USB_DT_HUB_NONVAR_SIZE + portArrLen))
+		memset(buf + USB_DT_HUB_NONVAR_SIZE + portArrLen, 0xff, l - (USB_DT_HUB_NONVAR_SIZE + portArrLen));
+	}
 	}
 	else l = len;
 
 	desc.bDescLength = l;
 	desc.bDescriptorType = 0x29;
-	desc.bNbrPorts = vhc->port_count;
+	desc.bNbrPorts = vhc->usb2_port_count;
 	desc.wHubCharacteristics = __constant_cpu_to_le16(0x0009); // Per port power and overcurrent
 	memcpy(buf, &desc, l);
 }
@@ -396,6 +565,10 @@ static int vhci_hub_control(struct usb_h
 	u16 *ps, *pc;
 	u8 *pf;
 	u8 port, has_changes = 0;
+	struct usb_vhci_port *ports;
+	u8 port_offset = 0;
+	u8 port_count = 0;
+	u32 pstat = hcd->speed >= HCD_USB3 ? USB_SS_PORT_STAT_POWER : USB_PORT_STAT_POWER;
 
 	vhc = usbhcd_to_vhcihcd(hcd);
 	dev = vhcihcd_to_dev(vhc);
@@ -405,6 +578,8 @@ static int vhci_hub_control(struct usb_h
 	if(unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)))
 		return -ETIMEDOUT;
 
+	get_vhci_params(hcd, vhc, &port_count, &ports, NULL, &port_offset);
+
 	spin_lock_irqsave(&vhc->lock, flags);
 
 	switch(typeReq)
@@ -421,14 +596,19 @@ static int vhci_hub_control(struct usb_h
 #ifdef DEBUG
 		if(debug_output) dev_dbg(dev, "%s: ClearPortFeature [wValue=0x%04x, wIndex=%d]\n", __FUNCTION__, (int)wValue, (int)wIndex);
 #endif
-		if(unlikely(!wIndex || wIndex > vhc->port_count || wLength))
+		if(unlikely(!wIndex || wIndex > port_count || wLength))
 			goto err;
-		ps = &vhc->ports[wIndex - 1].port_status;
-		pc = &vhc->ports[wIndex - 1].port_change;
-		pf = &vhc->ports[wIndex - 1].port_flags;
+		ps = &ports[wIndex - 1].port_status;
+		pc = &ports[wIndex - 1].port_change;
+		pf = &ports[wIndex - 1].port_flags;
 		switch(wValue)
 		{
 		case USB_PORT_FEAT_SUSPEND:
+			if(hcd->speed >= HCD_USB3)
+			{
+				dev_info(dev, "ClearPortFeature: USB_PORT_FEAT_SUSPEND not support by USB3.0\n");
+				goto err;
+			}
 			// (see USB 2.0 spec section 11.5 and 11.24.2.7.1.3)
 			if(*ps & USB_PORT_STAT_SUSPEND)
 			{
@@ -436,12 +616,11 @@ static int vhci_hub_control(struct usb_h
 				if(debug_output) dev_dbg(dev, "Port %d resuming\n", (int)wIndex);
 #endif
 				*pf |= USB_VHCI_PORT_STAT_FLAG_RESUMING;
-				vhci_port_update(vhc, wIndex);
+				vhci_port_update(vhc, wIndex + port_offset);
 			}
 			break;
 		case USB_PORT_FEAT_POWER:
-			// (see USB 2.0 spec section 11.11 and 11.24.2.7.1.6)
-			if(*ps & USB_PORT_STAT_POWER)
+			if(*ps & pstat)
 			{
 #ifdef DEBUG
 				if(debug_output) dev_dbg(dev, "Port %d power-off\n", (int)wIndex);
@@ -452,7 +631,7 @@ static int vhci_hub_control(struct usb_h
 				*pc &= USB_PORT_STAT_C_OVERCURRENT;
 				// clear resuming flag
 				*pf &= ~USB_VHCI_PORT_STAT_FLAG_RESUMING;
-				vhci_port_update(vhc, wIndex);
+				vhci_port_update(vhc, wIndex + port_offset);
 			}
 			break;
 		case USB_PORT_FEAT_ENABLE:
@@ -469,7 +648,7 @@ static int vhci_hub_control(struct usb_h
 				// clear resuming flag
 				*pf &= ~USB_VHCI_PORT_STAT_FLAG_RESUMING;
 				// TODO: maybe we should clear the low/high speed bits here (section 11.24.2.7.1.{7,8})
-				vhci_port_update(vhc, wIndex);
+				vhci_port_update(vhc, wIndex + port_offset);
 			}
 			break;
 		case USB_PORT_FEAT_CONNECTION:
@@ -478,16 +657,26 @@ static int vhci_hub_control(struct usb_h
 		case USB_PORT_FEAT_LOWSPEED:
 		case USB_PORT_FEAT_HIGHSPEED:
 		case USB_PORT_FEAT_INDICATOR:
+		//USB3.0
+		case USB_PORT_FEAT_LINK_STATE:
+		case USB_PORT_FEAT_U1_TIMEOUT:
+		case USB_PORT_FEAT_U2_TIMEOUT:
+		case USB_PORT_FEAT_BH_PORT_RESET:
+		case USB_PORT_FEAT_FORCE_LINKPM_ACCEPT:
 			break; // no-op
 		case USB_PORT_FEAT_C_CONNECTION:
 		case USB_PORT_FEAT_C_ENABLE:
 		case USB_PORT_FEAT_C_SUSPEND:
 		case USB_PORT_FEAT_C_OVER_CURRENT:
 		case USB_PORT_FEAT_C_RESET:
+		//USB3.0
+		case USB_PORT_FEAT_C_PORT_LINK_STATE:
+		case USB_PORT_FEAT_C_PORT_CONFIG_ERROR:
+		case USB_PORT_FEAT_C_BH_PORT_RESET:
 			if(*pc & (1 << (wValue - 16)))
 			{
 				*pc &= ~(1 << (wValue - 16));
-				vhci_port_update(vhc, wIndex);
+				vhci_port_update(vhc, wIndex + port_offset);
 			}
 			break;
 		//case USB_PORT_FEAT_TEST:
@@ -501,7 +690,29 @@ static int vhci_hub_control(struct usb_h
 #endif
 		if(unlikely(wIndex))
 			goto err;
-		hub_descriptor(vhc, buf, wLength);
+		if(hcd->speed >= HCD_USB3 &&
+                (wLength < USB_DT_SS_HUB_SIZE ||
+                 wValue != (USB_DT_SS_HUB << 8)))
+		{
+			dev_info(dev,
+                "Wrong hub descriptor type for "
+                "USB 3.0 roothub.\n");
+			goto err;
+		}
+		if (hcd->speed >= HCD_USB3)
+			ss_hub_descriptor(vhc, buf, wLength);
+		else
+			hub_descriptor(vhc, buf, wLength);
+		break;
+	case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
+		if (hcd->speed < HCD_USB3)
+			goto err;
+
+		if ((wValue >> 8) != USB_DT_BOS)
+			goto err;
+
+		memcpy(buf, &usb3_bos_desc, sizeof(usb3_bos_desc));
+		retval = sizeof(usb3_bos_desc);
 		break;
 	case GetHubStatus:
 #ifdef DEBUG
@@ -515,28 +726,34 @@ static int vhci_hub_control(struct usb_h
 #ifdef DEBUG
 		if(debug_output) dev_dbg(dev, "%s: GetPortStatus [wIndex=%d]\n", __FUNCTION__, (int)wIndex);
 #endif
-		if(unlikely(wValue || !wIndex || wIndex > vhc->port_count || wLength != 4))
+		if(unlikely(wValue || !wIndex || wIndex > port_count || wLength != 4))
 			goto err;
 #ifdef DEBUG
-		if(debug_output) dev_dbg(dev, "%s: ==> [port_status=0x%04x] [port_change=0x%04x]\n", __FUNCTION__, (int)vhc->ports[wIndex - 1].port_status, (int)vhc->ports[wIndex - 1].port_change);
+		if(debug_output) dev_dbg(dev, "%s: ==> [port_status=0x%04x] [port_change=0x%04x]\n", __FUNCTION__, (int)ports[wIndex - 1].port_status, (int)ports[wIndex - 1].port_change);
 #endif
-		buf[0] = (u8)vhc->ports[wIndex - 1].port_status;
-		buf[1] = (u8)(vhc->ports[wIndex - 1].port_status >> 8);
-		buf[2] = (u8)vhc->ports[wIndex - 1].port_change;
-		buf[3] = (u8)(vhc->ports[wIndex - 1].port_change >> 8);
+		buf[0] = (u8)ports[wIndex - 1].port_status;
+		buf[1] = (u8)(ports[wIndex - 1].port_status >> 8);
+		buf[2] = (u8)ports[wIndex - 1].port_change;
+		buf[3] = (u8)(ports[wIndex - 1].port_change >> 8);
 		break;
 	case SetPortFeature:
 #ifdef DEBUG
 		if(debug_output) dev_dbg(dev, "%s: SetPortFeature [wValue=0x%04x, wIndex=%d]\n", __FUNCTION__, (int)wValue, (int)wIndex);
 #endif
-		if(unlikely(!wIndex || wIndex > vhc->port_count || wLength))
+		if(unlikely(!wIndex || wIndex > port_count || wLength))
 			goto err;
-		ps = &vhc->ports[wIndex - 1].port_status;
-		pc = &vhc->ports[wIndex - 1].port_change;
-		pf = &vhc->ports[wIndex - 1].port_flags;
+		ps = &ports[wIndex - 1].port_status;
+		pc = &ports[wIndex - 1].port_change;
+		pf = &ports[wIndex - 1].port_flags;
 		switch(wValue)
 		{
 		case USB_PORT_FEAT_SUSPEND:
+			if (hcd->speed >= HCD_USB3) {
+				dev_info(dev,
+						"USB_PORT_FEAT_SUSPEND req not "
+						"supported for USB 3.0 roothub\n");
+				goto err;
+			}
 			// USB 2.0 spec section 11.24.2.7.1.3:
 			//  "This bit can be set only if the port’s PORT_ENABLE bit is set and the hub receives
 			//  a SetPortFeature(PORT_SUSPEND) request."
@@ -548,20 +765,28 @@ static int vhci_hub_control(struct usb_h
 				if(debug_output) dev_dbg(dev, "Port %d suspended\n", (int)wIndex);
 #endif
 				*ps |= USB_PORT_STAT_SUSPEND;
-				vhci_port_update(vhc, wIndex);
+				vhci_port_update(vhc, wIndex + port_offset);
 			}
 			break;
 		case USB_PORT_FEAT_POWER:
 			// (see USB 2.0 spec section 11.11 and 11.24.2.7.1.6)
-			if(!(*ps & USB_PORT_STAT_POWER))
+			if(!(*ps & pstat))
 			{
 #ifdef DEBUG
 				if(debug_output) dev_dbg(dev, "Port %d power-on\n", (int)wIndex);
 #endif
-				*ps |= USB_PORT_STAT_POWER;
-				vhci_port_update(vhc, wIndex);
+				*ps |= pstat;
+				vhci_port_update(vhc, wIndex + port_offset);
 			}
 			break;
+		case USB_PORT_FEAT_BH_PORT_RESET:
+			/* Applicable only for USB3.0 hub */
+			if (hcd->speed < HCD_USB3) {
+			dev_dbg(dev,
+				"USB_PORT_FEAT_BH_PORT_RESET req not "
+				 "supported for USB 2.0 roothub\n");
+			goto err;
+			}
 		case USB_PORT_FEAT_RESET:
 			// (see USB 2.0 spec section 11.24.2.7.1.5)
 			// initiate reset only if there is a device plugged into the port and if there isn't already a reset pending
@@ -571,24 +796,55 @@ static int vhci_hub_control(struct usb_h
 				if(debug_output) dev_dbg(dev, "Port %d resetting\n", (int)wIndex);
 #endif
 
-				// keep the state of these bits and clear all others
-				*ps &= USB_PORT_STAT_POWER
-				     | USB_PORT_STAT_CONNECTION
-				     | USB_PORT_STAT_LOW_SPEED
-				     | USB_PORT_STAT_HIGH_SPEED
-				     | USB_PORT_STAT_OVERCURRENT;
-
-				*ps |= USB_PORT_STAT_RESET; // reset initiated
+				if(hcd->speed >= HCD_USB3)
+				{
+					*ps = USB_SS_PORT_STAT_POWER |
+						USB_PORT_STAT_CONNECTION |
+						USB_PORT_STAT_RESET;
+				}
+				else
+				{
+					// keep the state of these bits and clear all others
+					*ps &= USB_PORT_STAT_POWER
+						| USB_PORT_STAT_CONNECTION
+						| USB_PORT_STAT_LOW_SPEED
+						| USB_PORT_STAT_HIGH_SPEED
+						| USB_PORT_STAT_OVERCURRENT;
 
+					*ps |= USB_PORT_STAT_RESET; // reset initiated
+				}
 				// clear resuming flag
 				*pf &= ~USB_VHCI_PORT_STAT_FLAG_RESUMING;
 
-				vhci_port_update(vhc, wIndex);
+				vhci_port_update(vhc, wIndex + port_offset);
 			}
 #ifdef DEBUG
 			else if(debug_output) dev_dbg(dev, "Port %d reset not possible because of port_state=%04x\n", (int)wIndex, (int)*ps);
 #endif
 			break;
+		case USB_PORT_FEAT_LINK_STATE:
+			if(hcd->speed < HCD_USB3)
+			{
+				dev_dbg(dev,
+				         "USB_PORT_FEAT_LINK_STATE req not "
+				         "supported for USB 2.0 roothub\n");
+				goto err;
+			}
+			/*
+			 * Since this is dummy we don't have an actual link so
+			 * there is nothing to do for the SET_LINK_STATE cmd
+			 */
+			break;
+		case USB_PORT_FEAT_U1_TIMEOUT:
+		case USB_PORT_FEAT_U2_TIMEOUT:
+			if(hcd->speed < HCD_USB3)
+			{
+				dev_dbg(dev,
+				         "USB_PORT_FEAT_U1/2_TIMEOUT req not "
+				         "supported for USB 2.0 roothub\n");
+				goto err;
+			}
+			break;
 		case USB_PORT_FEAT_CONNECTION:
 		case USB_PORT_FEAT_OVER_CURRENT:
 		case USB_PORT_FEAT_LOWSPEED:
@@ -600,10 +856,14 @@ static int vhci_hub_control(struct usb_h
 		case USB_PORT_FEAT_C_SUSPEND:
 		case USB_PORT_FEAT_C_OVER_CURRENT:
 		case USB_PORT_FEAT_C_RESET:
+		//USB3.0
+		case USB_PORT_FEAT_C_PORT_LINK_STATE:
+		case USB_PORT_FEAT_C_PORT_CONFIG_ERROR:
+		case USB_PORT_FEAT_C_BH_PORT_RESET:
 			if(!(*pc & (1 << (wValue - 16))))
 			{
 				*pc |= 1 << (wValue - 16);
-				vhci_port_update(vhc, wIndex);
+				vhci_port_update(vhc, wIndex + port_offset);
 			}
 			break;
 		//case USB_PORT_FEAT_ENABLE: // port can't be enabled without reseting (USB 2.0 spec section 11.24.2.7.1.2)
@@ -624,8 +884,8 @@ err:
 		retval = -EPIPE;
 	}
 
-	for(port = 0; port < vhc->port_count; port++)
-		if(vhc->ports[port].port_change)
+	for(port = 0; port < port_count; port++)
+		if(ports[port].port_change)
 			has_changes = 1;
 
 	spin_unlock_irqrestore(&vhc->lock, flags);
@@ -641,30 +901,36 @@ static int vhci_bus_suspend(struct usb_h
 	struct device *dev;
 	unsigned long flags;
 	u8 port;
+	struct usb_vhci_port *ports;
+	u8 port_count;
+	u8 port_offset;
+	u8 index;
 
 	vhc = usbhcd_to_vhcihcd(hcd);
 	dev = vhcihcd_to_dev(vhc);
 
 	trace_function(dev);
 
+	get_vhci_params(hcd, vhc, &port_count, &ports, &index, &port_offset);
+
 	spin_lock_irqsave(&vhc->lock, flags);
 
 	// suspend ports
-	for(port = 0; port < vhc->port_count; port++)
+	for(port = 0; port < port_count; port++)
 	{
-		if((vhc->ports[port].port_status & USB_PORT_STAT_ENABLE) &&
-			!(vhc->ports[port].port_status & USB_PORT_STAT_SUSPEND))
+		if((ports[port].port_status & USB_PORT_STAT_ENABLE) &&
+			!(ports[port].port_status & USB_PORT_STAT_SUSPEND))
 		{
 			dev_dbg(dev, "Port %d suspended\n", (int)port + 1);
-			vhc->ports[port].port_status |= USB_PORT_STAT_SUSPEND;
-			vhc->ports[port].port_flags &= ~USB_VHCI_PORT_STAT_FLAG_RESUMING;
-			vhci_port_update(vhc, port + 1);
+			ports[port].port_status |= USB_PORT_STAT_SUSPEND;
+			ports[port].port_flags &= ~USB_VHCI_PORT_STAT_FLAG_RESUMING;
+			vhci_port_update(vhc, port + 1 + port_offset);
 		}
 	}
 
 	// TODO: somehow we have to suppress the resuming of ports while the bus is suspended
 
-	vhc->rh_state = USB_VHCI_RH_SUSPENDED;
+	vhc->rh_state[index] = USB_VHCI_RH_SUSPENDED;
 	hcd->state = HC_STATE_SUSPENDED;
 
 	spin_unlock_irqrestore(&vhc->lock, flags);
@@ -678,6 +944,7 @@ static int vhci_bus_resume(struct usb_hc
 	struct device *dev;
 	int rc = 0;
 	unsigned long flags;
+	int index = (hcd->speed >= HCD_USB3)? 1 : 0;
 
 	vhc = usbhcd_to_vhcihcd(hcd);
 	dev = vhcihcd_to_dev(vhc);
@@ -692,7 +959,7 @@ static int vhci_bus_resume(struct usb_hc
 	}
 	else
 	{
-		vhc->rh_state = USB_VHCI_RH_RUNNING;
+		vhc->rh_state[index] = USB_VHCI_RH_RUNNING;
 		//set_link_state(vhc);
 		hcd->state = HC_STATE_RUNNING;
 	}
@@ -748,6 +1015,7 @@ static ssize_t show_urbs(struct device *
 	size_t size = 0;
 	unsigned long flags;
 	struct list_head *list;
+	int i;
 
 	pdev = to_platform_device(dev);
 	vhc = pdev_to_vhcihcd(pdev);
@@ -776,15 +1044,56 @@ static ssize_t show_urbs(struct device *
 		temp = PAGE_SIZE - size;
 		if(unlikely(temp <= 0)) break;
 
-		temp = show_urb(buf, temp, urbp->urb);
-		buf += temp;
-		size += temp;
+		for (i = 0; i < urbp->num_urbs; i++) {
+			temp = show_urb(buf, temp, urbp->urbs[i]);
+			buf += temp;
+			size += temp;
+		}
 	}
 	spin_unlock_irqrestore(&vhc->lock, flags);
 
 	return size;
 }
 
+static int vhci_setup(struct usb_hcd *hcd)
+{
+	struct usb_vhci_hcd *vhc;
+	vhc = usbhcd_to_vhcihcd(hcd);
+
+	hcd->irq = HCD_IRQ;
+	vhci_printk(KERN_INFO, "hcd will use irq %d\n", hcd->irq);
+
+	if(usb_hcd_is_primary_hcd(hcd))
+	{
+		vhc->hs_hcd = hcd;
+		/*
+		 * Mark the first roothub as being USB 2.0.
+		 * The USB 3.0 roothub will be registered later by
+		 * vhci_hcd_probe()
+		 */
+		hcd->speed = HCD_USB2;
+		hcd->self.root_hub->speed = USB_SPEED_HIGH;
+	}
+	else
+	{
+		vhc->ss_hcd = hcd;
+		hcd->speed = HCD_USB3;
+		hcd->self.root_hub->speed = USB_SPEED_SUPER;
+	}
+
+	return 0;
+}
+
+static int vhci_start_ss(struct usb_hcd *hcd)
+{
+	struct usb_vhci_hcd *vhc = usbhcd_to_vhcihcd(hcd);
+	vhc->rh_state[1] = USB_VHCI_RH_RUNNING;
+	hcd->power_budget = 0; // No limits
+	hcd->state = HC_STATE_RUNNING;
+	hcd->uses_new_polling = 1;
+	return 0;
+}
+
 static int vhci_start(struct usb_hcd *hcd)
 {
 	struct usb_vhci_hcd *vhc;
@@ -800,6 +1109,12 @@ static int vhci_start(struct usb_hcd *hc
 	vhc = usbhcd_to_vhcihcd(hcd);
 	vdev = vhcihcd_to_vhcidev(vhc);
 
+	if(!usb_hcd_is_primary_hcd(hcd))
+	{
+		vhci_start_ss(hcd);
+		return 0;
+	}
+
 	ports = kzalloc(vdev->port_count * sizeof(struct usb_vhci_port), GFP_KERNEL);
 	if(unlikely(ports == NULL)) return -ENOMEM;
 
@@ -807,17 +1122,28 @@ static int vhci_start(struct usb_hcd *hc
 	//init_timer(&vhc->timer);
 	//vhc->timer.function = vhci_timer;
 	//vhc->timer.data = (unsigned long)vhc;
+	vhc->disabled = false;
 	vhc->ports = ports;
-	vhc->port_count = vdev->port_count;
+	if(is_support_usb3)
+	{
+		vhc->usb3_port_count = vdev->port_count / 2;
+		vhc->usb2_port_count = vdev->port_count - vhc->usb3_port_count;
+	}
+	else
+	{
+		vhc->usb2_port_count = vdev->port_count;
+		vhc->usb3_port_count = 0;
+	}
+
 	vhc->port_update = 0;
 	atomic_set(&vhc->frame_num, 0);
 	INIT_LIST_HEAD(&vhc->urbp_list_inbox);
 	INIT_LIST_HEAD(&vhc->urbp_list_fetched);
 	INIT_LIST_HEAD(&vhc->urbp_list_cancel);
 	INIT_LIST_HEAD(&vhc->urbp_list_canceling);
-	vhc->rh_state = USB_VHCI_RH_RUNNING;
+	vhc->rh_state[0] = USB_VHCI_RH_RUNNING;
 
-	hcd->power_budget = 500; // NOTE: practically we have unlimited power because this is a virtual device with... err... virtual power!
+	hcd->power_budget = 0; // NOTE: practically we have unlimited power because this is a virtual device with... err... virtual power!
 	hcd->state = HC_STATE_RUNNING;
 	hcd->uses_new_polling = 1;
 #ifndef NO_HAS_TT_FLAG
@@ -847,7 +1173,8 @@ rem_file_inbox:
 kfree_port_arr:
 	kfree(ports);
 	vhc->ports = NULL;
-	vhc->port_count = 0;
+	vhc->usb2_port_count = 0;
+	vhc->usb3_port_count = 0;
 	return retval;
 }
 
@@ -860,6 +1187,10 @@ static void vhci_stop(struct usb_hcd *hc
 
 	trace_function(dev);
 
+	if(!usb_hcd_is_primary_hcd(hcd))
+		return;
+
+	hcd->shared_hcd = NULL;
 	vhc = usbhcd_to_vhcihcd(hcd);
 
 	device_remove_file(dev, &dev_attr_urbs_canceling);
@@ -871,10 +1202,12 @@ static void vhci_stop(struct usb_hcd *hc
 	{
 		kfree(vhc->ports);
 		vhc->ports = NULL;
-		vhc->port_count = 0;
+		vhc->usb2_port_count = 0;
+		vhc->usb3_port_count = 0;
 	}
 
-	vhc->rh_state = USB_VHCI_RH_RESET;
+	vhc->rh_state[0] = USB_VHCI_RH_RESET;
+	vhc->rh_state[1] = USB_VHCI_RH_RESET;
 	dev_info(dev, "stopped\n");
 }
 
@@ -886,13 +1219,14 @@ static int vhci_get_frame(struct usb_hcd
 	return atomic_read(&vhc->frame_num);
 }
 
-static const struct hc_driver vhci_hcd = {
+static struct hc_driver vhci_hcd = {
 	.description      = driver_name,
 	.product_desc     = "VHCI Host Controller",
 	.hcd_priv_size    = sizeof(struct usb_vhci_hcd),
 
-	.flags            = HCD_USB2,
+	.flags            = HCD_USB3 | HCD_SHARED,
 
+	.reset            = vhci_setup,
 	.start            = vhci_start,
 	.stop             = vhci_stop,
 
@@ -909,7 +1243,9 @@ static const struct hc_driver vhci_hcd =
 
 static int vhci_hcd_probe(struct platform_device *pdev)
 {
-	struct usb_hcd *hcd;
+	struct usb_hcd *hs_hcd;
+	struct usb_hcd *ss_hcd;
+
 	struct usb_vhci_device *vdev;
 	int retval;
 
@@ -921,30 +1257,58 @@ static int vhci_hcd_probe(struct platfor
 	dev_info(&pdev->dev, DRIVER_DESC " -- Version " DRIVER_VERSION "\n");
 	dev_info(&pdev->dev, "--> Backend: %s\n", vdev->ifc->ifc_desc);
 
-	hcd = usb_create_hcd(&vhci_hcd, &pdev->dev, vhci_dev_name(&pdev->dev));
-	if(unlikely(!hcd)) return -ENOMEM;
-	vdev->vhc = usbhcd_to_vhcihcd(hcd);
+	if (!is_support_usb3)
+		vhci_hcd.flags = HCD_USB2;
+
+	hs_hcd = usb_create_hcd(&vhci_hcd, &pdev->dev, vhci_dev_name(&pdev->dev));
+	if(unlikely(!hs_hcd)) return -ENOMEM;
+	vdev->vhc = usbhcd_to_vhcihcd(hs_hcd);
+
+	retval = usb_add_hcd(hs_hcd, 0, 0); // calls vhci_setup-->vhci_start
+	if(unlikely(retval)) goto put_usb2_hcd;
+
+	if(is_support_usb3)
+	{
+		ss_hcd = usb_create_shared_hcd(&vhci_hcd, &pdev->dev, vhci_dev_name(&pdev->dev), hs_hcd);
+		if(unlikely(!ss_hcd))
+		{
+			retval = -ENOMEM;
+			goto dealloc_usb2_hcd;
+		}
 
-	retval = usb_add_hcd(hcd, 0, 0); // calls vhci_start
-	if(unlikely(retval)) usb_put_hcd(hcd);
+		retval = usb_add_hcd(ss_hcd, 0, 0);
+		if (unlikely(retval)) goto put_usb3_hcd;
+	}
+	else
+	{
+		vhci_printk(KERN_INFO, "VHCI cannot support USB3.0.\n");
+	}
 
+	return 0;
+put_usb3_hcd:
+	usb_put_hcd(ss_hcd);
+dealloc_usb2_hcd:
+	usb_remove_hcd(hs_hcd);
+put_usb2_hcd:
+	usb_put_hcd(hs_hcd);
+	vdev->vhc->hs_hcd = vdev->vhc->ss_hcd = NULL;
 	return retval;
 }
 
 static int vhci_hcd_remove(struct platform_device *pdev)
 {
 	unsigned long flags;
-	struct usb_hcd *hcd;
 	struct usb_vhci_hcd *vhc;
 	struct usb_vhci_urb_priv *urbp;
 	struct usb_vhci_device *vdev;
 
 	vdev = pdev_to_vhcidev(pdev);
 	vhc = vhcidev_to_vhcihcd(vdev);
-	hcd = vhcidev_to_usbhcd(vdev);
 
 	trace_function(vhcihcd_to_dev(vhc));
 
+        vhc->disabled = true;
+
 	spin_lock_irqsave(&vhc->lock, flags);
 	while(!list_empty(&vhc->urbp_list_inbox))
 	{
@@ -972,8 +1336,14 @@ static int vhci_hcd_remove(struct platfo
 	}
 	spin_unlock_irqrestore(&vhc->lock, flags);
 
-	usb_remove_hcd(hcd); // calls vhci_stop
-	usb_put_hcd(hcd);
+	if(is_support_usb3)
+	{
+		usb_remove_hcd(vhc->ss_hcd); // calls vhci_stop
+		usb_put_hcd(vhc->ss_hcd);
+	}
+
+	usb_remove_hcd(vhc->hs_hcd); // calls vhci_stop
+	usb_put_hcd(vhc->hs_hcd);
 	vdev->vhc = NULL;
 
 	if(vdev->ifc->destroy)
@@ -987,38 +1357,52 @@ static int vhci_hcd_remove(struct platfo
 
 static int vhci_hcd_suspend(struct platform_device *pdev, pm_message_t state)
 {
-	struct usb_hcd *hcd;
 	struct usb_vhci_hcd *vhc;
 	int rc = 0;
 
 	vhc = pdev_to_vhcihcd(pdev);
-	hcd = vhcihcd_to_usbhcd(vhc);
 
 	trace_function(vhcihcd_to_dev(vhc));
 
-	if(unlikely(vhc->rh_state == USB_VHCI_RH_RUNNING))
+	if(is_support_usb3)
+	{
+		if(unlikely(vhc->rh_state[1] == USB_VHCI_RH_RUNNING))
+		{
+			dev_warn(&pdev->dev, "Root hub isn't suspended! You have to suspend the root hub before you suspend the host controller device.\n");
+			rc = -EBUSY;
+		}
+		else
+			clear_bit(HCD_FLAG_HW_ACCESSIBLE, &vhc->ss_hcd->flags);
+	}
+
+
+	if(unlikely(vhc->rh_state[0] == USB_VHCI_RH_RUNNING))
 	{
 		dev_warn(&pdev->dev, "Root hub isn't suspended! You have to suspend the root hub before you suspend the host controller device.\n");
 		rc = -EBUSY;
 	}
 	else
-		clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+		clear_bit(HCD_FLAG_HW_ACCESSIBLE, &vhc->hs_hcd->flags);
 
 	return rc;
 }
 
 static int vhci_hcd_resume(struct platform_device *pdev)
 {
-	struct usb_hcd *hcd;
 	struct usb_vhci_hcd *vhc;
 
 	vhc = pdev_to_vhcihcd(pdev);
-	hcd = vhcihcd_to_usbhcd(vhc);
 
 	trace_function(vhcihcd_to_dev(vhc));
 
-	set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
-	usb_hcd_poll_rh_status(hcd);
+	set_bit(HCD_FLAG_HW_ACCESSIBLE, &vhc->hs_hcd->flags);
+	usb_hcd_poll_rh_status(vhc->hs_hcd);
+
+	if(is_support_usb3)
+	{
+		set_bit(HCD_FLAG_HW_ACCESSIBLE, &vhc->ss_hcd->flags);
+		usb_hcd_poll_rh_status(vhc->ss_hcd);
+	}
 	return 0;
 }
 
@@ -1052,8 +1436,23 @@ int usb_vhci_hcd_register(const struct u
 	struct platform_device *pdev;
 	struct usb_vhci_device vdev, *vdev_ptr;
 
-	if(unlikely(port_count > 31))
+	if(unlikely(is_support_usb3 && port_count > 62))
+	{
+		vhci_printk(KERN_INFO, "the maximum number of ports is 62 when support USB 3.0\n");
+		return -EINVAL;
+	}
+
+	if(unlikely(!is_support_usb3 && port_count > 31))
+	{
+		vhci_printk(KERN_INFO, "the maximum number of ports is 31 when not support USB 3.0\n");
 		return -EINVAL;
+	}
+
+	if(unlikely(is_support_usb3 && port_count < 2))
+	{
+		vhci_printk(KERN_INFO, "please register vhci with port count > 1 if you want to support usb3.0\n");
+		return -EINVAL;
+	}
 
 	// search for free device-id
 	mutex_lock(&dev_enum_lock);
@@ -1174,10 +1573,13 @@ int usb_vhci_apply_port_stat(struct usb_
 	struct device *dev;
 	unsigned long flags;
 	u16 overcurrent;
+	bool is_usb3;
+	struct usb_hcd *hcd;
+	u32 pstat;
 
 	dev = vhcihcd_to_dev(vhc);
 
-	if(unlikely(!index || index > vhc->port_count))
+	if(unlikely(!index || index > (vhc->usb2_port_count + vhc->usb3_port_count)))
 		return -EINVAL;
 
 	if(unlikely(change != USB_PORT_STAT_C_CONNECTION &&
@@ -1189,12 +1591,23 @@ int usb_vhci_apply_port_stat(struct usb_
 		return -EINVAL;
 
 	spin_lock_irqsave(&vhc->lock, flags);
-	if(unlikely(!(vhc->ports[index - 1].port_status & USB_PORT_STAT_POWER)))
+
+	if(index > vhc->usb2_port_count)
+	{
+		is_usb3 = true;
+		pstat = USB_SS_PORT_STAT_POWER;
+	}
+	else
+	{
+		is_usb3 = false;
+		pstat = USB_PORT_STAT_POWER;
+	}
+
+	if(unlikely(!(vhc->ports[index - 1].port_status & pstat)))
 	{
 		spin_unlock_irqrestore(&vhc->lock, flags);
 		return -EPROTO;
 	}
-
 #ifdef DEBUG
 	if(debug_output) dev_dbg(dev, "performing PORT_STAT [port=%d ~status=0x%04x ~change=0x%04x]\n", (int)index, (int)status, (int)change);
 #endif
@@ -1204,13 +1617,23 @@ int usb_vhci_apply_port_stat(struct usb_
 	case USB_PORT_STAT_C_CONNECTION:
 		overcurrent = vhc->ports[index - 1].port_status & USB_PORT_STAT_OVERCURRENT;
 		vhc->ports[index - 1].port_change |= USB_PORT_STAT_C_CONNECTION;
-		if(status & USB_PORT_STAT_CONNECTION)
-			vhc->ports[index - 1].port_status = USB_PORT_STAT_POWER | USB_PORT_STAT_CONNECTION |
-				((status & USB_PORT_STAT_LOW_SPEED) ? USB_PORT_STAT_LOW_SPEED :
-				((status & USB_PORT_STAT_HIGH_SPEED) ? USB_PORT_STAT_HIGH_SPEED : 0)) |
-				overcurrent;
+		if(status & USB_PORT_STAT_CONNECTION) {
+			if(is_usb3)
+				vhc->ports[index - 1].port_status = USB_SS_PORT_STAT_POWER;
+			else
+				vhc->ports[index - 1].port_status = USB_PORT_STAT_POWER |
+					((status & USB_PORT_STAT_LOW_SPEED) ? USB_PORT_STAT_LOW_SPEED :
+					((status & USB_PORT_STAT_HIGH_SPEED) ? USB_PORT_STAT_HIGH_SPEED : 0));
+
+			vhc->ports[index - 1].port_status |= USB_PORT_STAT_CONNECTION |	overcurrent;
+		}
 		else
-			vhc->ports[index - 1].port_status = USB_PORT_STAT_POWER | overcurrent;
+		{
+			if(is_usb3)
+				vhc->ports[index - 1].port_status = USB_SS_PORT_STAT_POWER | overcurrent;
+			else
+				vhc->ports[index - 1].port_status = USB_PORT_STAT_POWER | overcurrent;
+		}
 		vhc->ports[index - 1].port_flags &= ~USB_VHCI_PORT_STAT_FLAG_RESUMING;
 		break;
 
@@ -1274,14 +1697,15 @@ int usb_vhci_apply_port_stat(struct usb_
 
 	vhci_port_update(vhc, index);
 	spin_unlock_irqrestore(&vhc->lock, flags);
+	hcd = is_usb3 ? vhc->ss_hcd : vhc->hs_hcd;
+	usb_hcd_poll_rh_status(hcd);
 
-	usb_hcd_poll_rh_status(vhcihcd_to_usbhcd(vhc));
 	return 0;
 }
 EXPORT_SYMBOL_GPL(usb_vhci_apply_port_stat);
 
 #ifdef DEBUG
-static ssize_t show_debug_output(struct device_driver *drv, char *buf)
+static ssize_t debug_output_show(struct device_driver *drv, char *buf)
 {
 	if(buf != NULL)
 	{
@@ -1296,7 +1720,7 @@ static ssize_t show_debug_output(struct
 	return 1;
 }
 
-static ssize_t store_debug_output(struct device_driver *drv, const char *buf, size_t count)
+static ssize_t debug_output_store(struct device_driver *drv, const char *buf, size_t count)
 {
 	if(count != 1 || buf == NULL) return -EINVAL;
 	switch(*buf)
@@ -1309,7 +1733,11 @@ static ssize_t store_debug_output(struct
 	return -EINVAL;
 }
 
-static DRIVER_ATTR(debug_output, S_IRUSR | S_IWUSR, show_debug_output, store_debug_output);
+#ifdef DRIVER_ATTR_RW
+static DRIVER_ATTR_RW(debug_output);
+#else
+static DRIVER_ATTR(debug_output, S_IRUSR | S_IWUSR, debug_output_show, debug_output_store);
+#endif
 #endif
 
 static int __init init(void)
diff -uprN vhci-hcd-1.15/usb-vhci-hcd.h vhci-hcd-1.15.patch/usb-vhci-hcd.h
--- vhci-hcd-1.15/usb-vhci-hcd.h	2013-06-21 08:02:38.000000000 +0800
+++ vhci-hcd-1.15.patch/usb-vhci-hcd.h	2024-05-07 11:10:42.631267951 +0800
@@ -94,20 +94,25 @@ struct usb_vhci_device
 
 struct usb_vhci_urb_priv
 {
-	struct urb *urb;
 	struct list_head urbp_list;
 	atomic_t status;
+	int num_urbs;
+	struct urb *urbs[0];
 };
 
 struct usb_vhci_hcd
 {
+	struct usb_hcd *hs_hcd;
+	struct usb_hcd *ss_hcd;
+
 	struct usb_vhci_port *ports;
-	u32 port_update;
+	u64 port_update;
 
 	spinlock_t lock;
+        bool disabled;
 
 	atomic_t frame_num;
-	enum usb_vhci_rh_state rh_state;
+	enum usb_vhci_rh_state rh_state[2];
 
 	// TODO: implement timer for incrementing frame_num every millisecond
 	//struct timer_list timer;
@@ -126,7 +131,8 @@ struct usb_vhci_hcd
 	// user space already knows about the cancelation state are in this list
 	struct list_head urbp_list_canceling;
 
-	u8 port_count;
+	u8 usb2_port_count;
+	u8 usb3_port_count;
 };
 
 static inline struct usb_vhci_device *pdev_to_vhcidev(struct platform_device *pdev)
@@ -166,7 +172,14 @@ static inline struct usb_hcd *vhcidev_to
 
 static inline struct usb_vhci_hcd *usbhcd_to_vhcihcd(struct usb_hcd *hcd)
 {
-	return (struct usb_vhci_hcd *)&hcd->hcd_priv;
+	struct usb_hcd *primary_hcd;
+
+	if (usb_hcd_is_primary_hcd(hcd))
+	        primary_hcd = hcd;
+	else
+	        primary_hcd = hcd->primary_hcd;
+
+	return (struct usb_vhci_hcd *) (primary_hcd->hcd_priv);
 }
 
 static inline struct device *usbhcd_to_dev(struct usb_hcd *hcd)
diff -uprN vhci-hcd-1.15/usb-vhci-iocifc.c vhci-hcd-1.15.patch/usb-vhci-iocifc.c
--- vhci-hcd-1.15/usb-vhci-iocifc.c	2013-06-21 08:02:38.000000000 +0800
+++ vhci-hcd-1.15.patch/usb-vhci-iocifc.c	2024-05-07 11:21:52.437118715 +0800
@@ -34,6 +34,8 @@
 #include <linux/platform_device.h>
 #include <linux/usb.h>
 #include <linux/fs.h>
+#include <linux/uaccess.h>
+#include <linux/version.h>
 
 #include "usb-vhci-hcd.h"
 
@@ -71,6 +73,20 @@
 #	define trace_function(dev) do {} while(0)
 #endif
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 0, 0)
+#	if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0)
+#		define vhci_access_ok(x, y, z) 					\
+		({								\
+		 	WARN_ON_IN_IRQ();					\
+			likely(!__range_not_ok(y, z, user_addr_max()));		\
+ 		})
+#	else
+#		define vhci_access_ok(x, y, z) access_ok(x, y, z)
+#	endif
+#else
+#	define vhci_access_ok(x, y, z) access_ok(y, z)
+#endif
+
 static const char driver_name[] = DRIVER_NAME;
 static const char driver_desc[] = DRIVER_DESC;
 #ifdef DEBUG
@@ -80,6 +96,7 @@ static unsigned int debug_output = 0;
 MODULE_DESCRIPTION(DRIVER_DESC " driver");
 MODULE_AUTHOR("Michael Singer <michael@a-singer.de>");
 MODULE_LICENSE("GPL");
+MODULE_VERSION("1.15.0");
 
 struct vhci_ifc_priv
 {
@@ -216,7 +233,7 @@ static int ioc_register(struct file *fil
 	if(copy_to_user(arg->bus_id, dname, i))
 	{
 		vhci_printk(KERN_WARNING, "Failed to copy bus_id to userspace.\n");
-		__put_user('\0', arg->bus_id);
+		__put_user('\0', (char*)arg->bus_id);
 	}
 	// make sure the last character is null
 	__put_user('\0', arg->bus_id + i);
@@ -301,7 +318,7 @@ static inline u16 conv_urb_flags(unsigne
 #ifdef DEBUG
 #include "usb-vhci-dump-urb.c"
 #else
-static inline void dump_urb(struct urb *urb) {/* do nothing */}
+static inline void dump_urbs(struct urb **urbs, int num_urbs) {/* do nothing */}
 #endif
 
 // called in device_ioctl only
@@ -310,6 +327,7 @@ static int ioc_fetch_work(struct usb_vhc
 #ifdef DEBUG
 	struct device *dev = vhcihcd_to_dev(vhc);
 #endif
+	struct usb_device *udev;
 	struct usb_vhci_urb_priv *urbp;
 	struct vhci_ifc_priv *ifcp;
 	struct usb_vhci_port port_stat;
@@ -318,6 +336,7 @@ static int ioc_fetch_work(struct usb_vhc
 	unsigned long flags;
 	long wret;
 	u8 _port, port;
+	int i;
 
 #ifdef DEBUG
 	// Floods the logs
@@ -354,9 +373,9 @@ static int ioc_fetch_work(struct usb_vhc
 	{
 		urbp = list_entry(vhc->urbp_list_cancel.next, struct usb_vhci_urb_priv, urbp_list);
 #ifdef DEBUG
-		if(debug_output) dev_dbg(dev, "cmd=USB_VHCI_HCD_IOCFETCHWORK [work=CANCEL_URB handle=0x%016llx]\n", (u64)(unsigned long)urbp->urb);
+		if(debug_output) dev_dbg(dev, "cmd=USB_VHCI_HCD_IOCFETCHWORK [work=CANCEL_URB handle=0x%016llx]\n", (u64)(unsigned long)urbp->urbs);
 #endif
-		handle = (u64)(unsigned long)urbp->urb;
+		handle = (u64)(unsigned long)urbp->urbs;
 		list_move_tail(&urbp->urbp_list, &vhc->urbp_list_canceling);
 		spin_unlock_irqrestore(&vhc->lock, flags);
 		__put_user(USB_VHCI_WORK_TYPE_CANCEL_URB, &arg->type);
@@ -366,16 +385,17 @@ static int ioc_fetch_work(struct usb_vhc
 
 	if(vhc->port_update)
 	{
-		if(ifcp->port_sched_offset >= vhc->port_count)
+		u8 total_ports = vhc->usb2_port_count + vhc->usb3_port_count;
+		if(ifcp->port_sched_offset >= total_ports)
 			ifcp->port_sched_offset = 0;
-		for(_port = 0; _port < vhc->port_count; _port++)
+		for(_port = 0; _port < total_ports; _port++)
 		{
 			// The port which will be checked first, is rotated by port_sched_offset, so that every port
 			// has its chance to be reported to user space, even if the hcd is under heavy load.
-			port = (_port + ifcp->port_sched_offset) % vhc->port_count;
-			if(vhc->port_update & (1 << (port + 1)))
+			port = (_port + ifcp->port_sched_offset) % total_ports;
+			if(vhc->port_update & ((u64)1 << (port + 1)))
 			{
-				vhc->port_update &= ~(1 << (port + 1));
+				vhc->port_update &= ~((u64)1 << (port + 1));
 				ifcp->port_sched_offset = port + 1;
 				port_stat = vhc->ports[port];
 				spin_unlock_irqrestore(&vhc->lock, flags);
@@ -396,32 +416,37 @@ repeat:
 	if(!list_empty(&vhc->urbp_list_inbox))
 	{
 		urbp = list_entry(vhc->urbp_list_inbox.next, struct usb_vhci_urb_priv, urbp_list);
-		handle = (u64)(unsigned long)urbp->urb;
+		udev = urbp->urbs[0]->dev;
+		handle = (u64)(unsigned long)urbp->urbs;
 		memset(&urb, 0, sizeof urb);
-		urb.address = usb_pipedevice(urbp->urb->pipe);
-		urb.endpoint = usb_pipeendpoint(urbp->urb->pipe) | (usb_pipein(urbp->urb->pipe) ? 0x80 : 0x00);
-		urb.type = conv_urb_type(usb_pipetype(urbp->urb->pipe));
-		urb.flags = conv_urb_flags(urbp->urb->transfer_flags);
-		if(usb_pipecontrol(urbp->urb->pipe))
+		urb.address = (udev->speed >= USB_SPEED_SUPER ? 0x80 : 0x00) | usb_pipedevice(urbp->urbs[0]->pipe);
+		urb.endpoint = usb_pipeendpoint(urbp->urbs[0]->pipe) | (usb_pipein(urbp->urbs[0]->pipe) ? 0x80 : 0x00);
+		urb.type = conv_urb_type(usb_pipetype(urbp->urbs[0]->pipe));
+		urb.flags = conv_urb_flags(urbp->urbs[0]->transfer_flags);
+#ifdef DEBUG
+		if(debug_output) dev_dbg(dev, "urb.address 0x%0x, urb.endpoint 0x%0x, urb.type 0x%0x, urb.flags 0x%0x\n",
+									urb.address, urb.endpoint, urb.type, urb.flags);
+#endif
+		if(usb_pipecontrol(urbp->urbs[0]->pipe))
 		{
 			const struct usb_ctrlrequest *cmd;
 			u16 wValue, wIndex, wLength;
-			if(unlikely(!urbp->urb->setup_packet))
+			if(unlikely(!urbp->urbs[0]->setup_packet))
 				goto invalid_urb;
-			cmd = (struct usb_ctrlrequest *)urbp->urb->setup_packet;
+			cmd = (struct usb_ctrlrequest *)urbp->urbs[0]->setup_packet;
 			wValue = le16_to_cpu(cmd->wValue);
 			wIndex = le16_to_cpu(cmd->wIndex);
 			wLength = le16_to_cpu(cmd->wLength);
-			if(unlikely(wLength > urbp->urb->transfer_buffer_length))
+			if(unlikely(wLength > urbp->urbs[0]->transfer_buffer_length))
 				goto invalid_urb;
 			if(cmd->bRequestType & 0x80)
 			{
-				if(unlikely(!wLength || !urbp->urb->transfer_buffer))
+				if(unlikely(!wLength || !urbp->urbs[0]->transfer_buffer))
 					goto invalid_urb;
 			}
 			else
 			{
-				if(unlikely(wLength && !urbp->urb->transfer_buffer))
+				if(unlikely(wLength && !urbp->urbs[0]->transfer_buffer))
 					goto invalid_urb;
 			}
 			urb.buffer_length = wLength;
@@ -431,27 +456,50 @@ repeat:
 			urb.setup_packet.wIndex = wIndex;
 			urb.setup_packet.wLength = wLength;
 		}
+		else if(usb_pipebulk(urbp->urbs[0]->pipe))
+		{
+			urb.buffer_length = 0;
+			for(i = 0; i < urbp->num_urbs; i++)
+			{
+				if(usb_pipein(urbp->urbs[i]->pipe))
+				{
+					if(unlikely(!urbp->urbs[i]->transfer_buffer_length || !urbp->urbs[i]->transfer_buffer))
+						goto invalid_urb;
+				}
+				else
+				{
+					if(unlikely(urbp->urbs[i]->transfer_buffer_length && !urbp->urbs[i]->transfer_buffer))
+						goto invalid_urb;
+				}
+				urb.buffer_length += urbp->urbs[i]->transfer_buffer_length;
+			}
+		}
 		else
 		{
-			if(usb_pipein(urbp->urb->pipe))
+			if(usb_pipein(urbp->urbs[0]->pipe))
 			{
-				if(unlikely(!urbp->urb->transfer_buffer_length || !urbp->urb->transfer_buffer))
+				if(unlikely(!urbp->urbs[0]->transfer_buffer_length || !urbp->urbs[0]->transfer_buffer))
 					goto invalid_urb;
 			}
 			else
 			{
-				if(unlikely(urbp->urb->transfer_buffer_length && !urbp->urb->transfer_buffer))
+				if(unlikely(urbp->urbs[0]->transfer_buffer_length && !urbp->urbs[0]->transfer_buffer))
 					goto invalid_urb;
 			}
-			urb.buffer_length = urbp->urb->transfer_buffer_length;
+			urb.buffer_length = urbp->urbs[0]->transfer_buffer_length;
 		}
-		urb.interval = urbp->urb->interval;
-		urb.packet_count = urbp->urb->number_of_packets;
+
+		urb.interval = urbp->urbs[0]->interval;
+		urb.packet_count = urbp->urbs[0]->number_of_packets;
 
 #ifdef DEBUG
-		if(debug_output) dev_dbg(dev, "cmd=USB_VHCI_HCD_IOCFETCHWORK [work=PROCESS_URB handle=0x%016llx]\n", handle);
+		if(debug_output)
+		{
+			dev_dbg(dev, "cmd=USB_VHCI_HCD_IOCFETCHWORK [work=PROCESS_URB handle=0x%016llx]\n", handle);
+			vhci_printk(KERN_INFO, "%s: buffer_length %d, interval %d, packet_count %d\n", __FUNCTION__, urb.buffer_length, urb.interval, urb.packet_count);
+		}
 #endif
-		dump_urb(urbp->urb);
+		dump_urbs(urbp->urbs, urbp->num_urbs);
 		list_move_tail(&urbp->urbp_list, &vhc->urbp_list_fetched);
 		spin_unlock_irqrestore(&vhc->lock, flags);
 
@@ -480,7 +528,7 @@ static inline struct usb_vhci_urb_priv *
 {
 	struct usb_vhci_urb_priv *entry;
 	list_for_each_entry(entry, &vhc->urbp_list_fetched, urbp_list)
-		if(entry->urb == handle)
+		if(entry->urbs == handle)
 			return entry;
 	return NULL;
 }
@@ -490,7 +538,7 @@ static inline struct usb_vhci_urb_priv *
 {
 	struct usb_vhci_urb_priv *entry;
 	list_for_each_entry(entry, &vhc->urbp_list_cancel, urbp_list)
-		if(entry->urb == handle)
+		if(entry->urbs == handle)
 			return entry;
 	return NULL;
 }
@@ -500,7 +548,7 @@ static inline struct usb_vhci_urb_priv *
 {
 	struct usb_vhci_urb_priv *entry;
 	list_for_each_entry(entry, &vhc->urbp_list_canceling, urbp_list)
-		if(entry->urb == handle)
+		if(entry->urbs == handle)
 			return entry;
 	return NULL;
 }
@@ -530,7 +578,8 @@ static int ioc_giveback_common(struct us
 #ifdef DEBUG
 	struct device *dev = vhcihcd_to_dev(vhc);
 #endif
-
+	int total_tb_length = 0;
+	u8 *buf_tmp;
 	// TODO: do we really need to disable interrupts for accessing the urb lists?
 	spin_lock_irqsave(&vhc->lock, flags);
 
@@ -564,12 +613,19 @@ static int ioc_giveback_common(struct us
 	// the list entry, because it calls list_del(), too!
 	INIT_LIST_HEAD(&urbp->urbp_list);
 
-	is_in = is_urb_dir_in(urbp->urb);
-	is_iso = usb_pipeisoc(urbp->urb->pipe);
+	is_in = is_urb_dir_in(urbp->urbs[0]);
+	is_iso = usb_pipeisoc(urbp->urbs[0]->pipe);
 
+	for(i = 0; i < urbp->num_urbs; i++)
+	{
+		total_tb_length += urbp->urbs[i]->transfer_buffer_length;
+	}
+#ifdef DEBUG
+	if(debug_output) dev_dbg(dev, "%s-%d: total_tb_length = %d, act = %d\n", __FUNCTION__, __LINE__, total_tb_length, act);
+#endif
 	if(likely(is_iso))
 	{
-		if(unlikely(is_in && act != urbp->urb->transfer_buffer_length))
+		if(unlikely(is_in && act != total_tb_length))
 		{
 #ifdef DEBUG
 			if(debug_output) dev_dbg(dev, "GIVEBACK(ISO): invalid: buffer_actual != buffer_length\n");
@@ -577,7 +633,7 @@ static int ioc_giveback_common(struct us
 			retval = -ENOBUFS;
 			goto done_with_errors;
 		}
-		if(unlikely(iso_count != urbp->urb->number_of_packets))
+		if(unlikely(iso_count != urbp->urbs[0]->number_of_packets))
 		{
 #ifdef DEBUG
 			if(debug_output) dev_dbg(dev, "GIVEBACK(ISO): invalid: number_of_packets missmatch\n");
@@ -595,14 +651,14 @@ static int ioc_giveback_common(struct us
 		}
 		if(likely(iso_count))
 		{
-			if(!access_ok(VERIFY_READ, (void *)iso, iso_count * sizeof(struct usb_vhci_ioc_iso_packet_giveback)))
+			if(!vhci_access_ok(VERIFY_READ, (void *)iso, iso_count * sizeof(struct usb_vhci_ioc_iso_packet_giveback)))
 			{
 				retval = -EFAULT;
 				goto done_with_errors;
 			}
 		}
 	}
-	else if(unlikely(act > urbp->urb->transfer_buffer_length))
+	else if(unlikely(act > total_tb_length))
 	{
 #ifdef DEBUG
 		if(debug_output) dev_dbg(dev, "GIVEBACK: invalid: buffer_actual > buffer_length\n");
@@ -620,13 +676,22 @@ static int ioc_giveback_common(struct us
 			retval = -EINVAL;
 			goto done_with_errors;
 		}
-		if(unlikely(copy_from_user(urbp->urb->transfer_buffer, buf, act)))
+
+		buf_tmp = (u8 *)buf;
+		for(i = 0; i < urbp->num_urbs; i++)
 		{
+			int actual_copy_len = (act < urbp->urbs[i]->transfer_buffer_length)? act : urbp->urbs[i]->transfer_buffer_length;
+			if(unlikely(copy_from_user(urbp->urbs[i]->transfer_buffer, buf_tmp, actual_copy_len)))
+			{
 #ifdef DEBUG
-			if(debug_output) dev_dbg(dev, "GIVEBACK: copy_from_user(buf) failed\n");
+				if(debug_output) dev_dbg(dev, "GIVEBACK: copy_from_user(buf) failed\n");
 #endif
-			retval = -EFAULT;
-			goto done_with_errors;
+				retval = -EFAULT;
+				goto done_with_errors;
+			}
+			urbp->urbs[i]->actual_length = actual_copy_len;
+			urbp->urbs[i]->error_count = err_count;
+			buf_tmp += actual_copy_len;
 		}
 	}
 	else if(unlikely(buf))
@@ -638,17 +703,25 @@ static int ioc_giveback_common(struct us
 		retval = -EINVAL;
 		goto done_with_errors;
 	}
+	else
+	{
+		for(i = 0; i < urbp->num_urbs; ++i)
+		{
+			urbp->urbs[i]->actual_length = urbp->urbs[i]->transfer_buffer_length;
+			urbp->urbs[i]->error_count = err_count;
+		}
+	}
 	if(likely(is_iso && iso_count))
 	{
 		for(i = 0; i < iso_count; i++)
 		{
-			__get_user(urbp->urb->iso_frame_desc[i].status, &iso[i].status);
-			__get_user(urbp->urb->iso_frame_desc[i].actual_length, &iso[i].packet_actual);
+			__get_user(urbp->urbs[0]->iso_frame_desc[i].status, &iso[i].status);
+			__get_user(urbp->urbs[0]->iso_frame_desc[i].actual_length, &iso[i].packet_actual);
 		}
 	}
-	urbp->urb->actual_length = act;
-	urbp->urb->error_count = err_count;
-
+#ifdef DEBUG
+	vhci_dbg("%s: actual_length %d, error_count %d, iso_count %d, status %d\n", __FUNCTION__, act, err_count, iso_count, status);
+#endif
 	// now we are done with this urb and it can return to its creator
 	usb_vhci_maybe_set_status(urbp, status);
 	spin_lock_irqsave(&vhc->lock, flags);
@@ -714,16 +787,20 @@ static int ioc_fetch_data_common(struct
 	int tb_len, is_in, is_iso, i, ret = -ENOMEM;
 	void *user_buf_tmp = NULL;
 	struct usb_vhci_ioc_iso_packet_data *iso_tmp = NULL;
+	int iso_tmp_len = iso_count * sizeof *iso_tmp;
+	u8 *buf_tmp = NULL;
+	tb_len = 0;
 
 	if(likely(user_len))
 	{
-		user_buf_tmp = kmalloc(user_len, GFP_KERNEL);
+		user_buf_tmp = (void *)__get_free_pages(GFP_KERNEL, get_order(user_len));
 		if(unlikely(!user_buf_tmp))
 			goto end;
 	}
 	if(likely(iso_count))
 	{
-		iso_tmp = kmalloc(iso_count * sizeof *iso_tmp, GFP_KERNEL);
+		iso_tmp = (struct usb_vhci_ioc_iso_packet_data *)
+					__get_free_pages(GFP_KERNEL, get_order(iso_tmp_len));
 		if(unlikely(!iso_tmp))
 			goto end;
 	}
@@ -746,19 +823,26 @@ static int ioc_fetch_data_common(struct
 		goto end_unlock;
 	}
 
-	tb_len = urbp->urb->transfer_buffer_length;
-	if(unlikely(usb_pipecontrol(urbp->urb->pipe)))
+	for(i = 0; i < urbp->num_urbs; ++i)
 	{
-		const struct usb_ctrlrequest *cmd = (struct usb_ctrlrequest *)urbp->urb->setup_packet;
+		tb_len += urbp->urbs[i]->transfer_buffer_length;
+	}
+#ifdef DEBUG
+	if(debug_output) dev_dbg(vhcihcd_to_dev(vhc), "%s: tb_len %d, iso_count %d\n", __FUNCTION__, tb_len, iso_count);
+#endif
+
+	if(unlikely(usb_pipecontrol(urbp->urbs[0]->pipe)))
+	{
+		const struct usb_ctrlrequest *cmd = (struct usb_ctrlrequest *)urbp->urbs[0]->setup_packet;
 		tb_len = le16_to_cpu(cmd->wLength);
 	}
 
-	is_in = is_urb_dir_in(urbp->urb);
-	is_iso = usb_pipeisoc(urbp->urb->pipe);
+	is_in = is_urb_dir_in(urbp->urbs[0]);
+	is_iso = usb_pipeisoc(urbp->urbs[0]->pipe);
 
 	if(likely(is_iso))
 	{
-		if(unlikely(iso_count != urbp->urb->number_of_packets))
+		if(unlikely(iso_count != urbp->urbs[0]->number_of_packets))
 		{
 			ret = -EINVAL;
 			goto end_unlock;
@@ -772,12 +856,12 @@ static int ioc_fetch_data_common(struct
 			}
 			for(i = 0; i < iso_count; i++)
 			{
-				iso_tmp[i].offset = urbp->urb->iso_frame_desc[i].offset;
-				iso_tmp[i].packet_length = urbp->urb->iso_frame_desc[i].length;
+				iso_tmp[i].offset = urbp->urbs[0]->iso_frame_desc[i].offset;
+				iso_tmp[i].packet_length = urbp->urbs[0]->iso_frame_desc[i].length;
 			}
 		}
 	}
-	else if(unlikely(is_in || !tb_len || !urbp->urb->transfer_buffer))
+	else if(unlikely(is_in || !tb_len || !urbp->urbs[0]->transfer_buffer))
 	{
 		ret = -ENODATA;
 		goto end_unlock;
@@ -790,7 +874,12 @@ static int ioc_fetch_data_common(struct
 			ret = -EINVAL;
 			goto end_unlock;
 		}
-		memcpy(user_buf_tmp, urbp->urb->transfer_buffer, tb_len);
+		buf_tmp = (u8 *)user_buf_tmp;
+		for(i = 0; i < urbp->num_urbs; ++i)
+		{
+			memcpy(buf_tmp, urbp->urbs[i]->transfer_buffer, urbp->urbs[i]->transfer_buffer_length);
+			buf_tmp += urbp->urbs[i]->transfer_buffer_length;
+		}
 	}
 
 	// we have copied all data into our private buffers, so we can release the spinlock
@@ -820,8 +909,8 @@ static int ioc_fetch_data_common(struct
 end_unlock:
 	spin_unlock_irqrestore(&vhc->lock, flags);
 end:
-	kfree(user_buf_tmp);
-	kfree(iso_tmp);
+	free_pages((unsigned long)user_buf_tmp, get_order(user_len));
+	free_pages((unsigned long)iso_tmp, get_order(iso_tmp_len));
 	return ret;
 }
 
@@ -855,6 +944,10 @@ static int ioc_fetch_data(struct usb_vhc
 	__get_user(user_buf, &arg->buffer);
 	__get_user(iso, &arg->iso_packets);
 	handle = (const void *)(unsigned long)handle64;
+#ifdef DEBUG
+	if(debug_output) dev_dbg(vhcihcd_to_dev(vhc), "%s: user_len %d, iso_count %d, user_buf %p, iso %p\n",
+								__FUNCTION__, user_len, iso_count, user_buf, iso);
+#endif
 	if(unlikely(!handle))
 		return -EINVAL;
 	return ioc_fetch_data_common(vhc, handle, user_buf, user_len, iso, iso_count);
@@ -933,9 +1026,9 @@ static long device_do_ioctl(struct file
 	if(unlikely(_IOC_TYPE(cmd) != USB_VHCI_HCD_IOC_MAGIC)) return -ENOTTY;
 	if(unlikely(_IOC_NR(cmd) > USB_VHCI_HCD_IOC_MAXNR)) return -ENOTTY;
 
-	if(unlikely((_IOC_DIR(cmd) & _IOC_READ) && !access_ok(VERIFY_WRITE, arg, _IOC_SIZE(cmd))))
+	if(unlikely((_IOC_DIR(cmd) & _IOC_READ) && !vhci_access_ok(VERIFY_WRITE, arg, _IOC_SIZE(cmd))))
 		return -EFAULT;
-	if(unlikely((_IOC_DIR(cmd) & _IOC_WRITE) && !access_ok(VERIFY_READ, arg, _IOC_SIZE(cmd))))
+	if(unlikely((_IOC_DIR(cmd) & _IOC_WRITE) && !vhci_access_ok(VERIFY_READ, arg, _IOC_SIZE(cmd))))
 		return -EFAULT;
 
 	if(unlikely(cmd == USB_VHCI_HCD_IOCREGISTER))
@@ -1024,7 +1117,7 @@ static struct file_operations fops = {
 };
 
 #ifdef DEBUG
-static ssize_t show_debug_output(struct device_driver *drv, char *buf)
+static ssize_t debug_output_show(struct device_driver *drv, char *buf)
 {
 	if(buf != NULL)
 	{
@@ -1039,7 +1132,7 @@ static ssize_t show_debug_output(struct
 	return 1;
 }
 
-static ssize_t store_debug_output(struct device_driver *drv, const char *buf, size_t count)
+static ssize_t debug_output_store(struct device_driver *drv, const char *buf, size_t count)
 {
 	if(count != 1 || buf == NULL) return -EINVAL;
 	switch(*buf)
@@ -1052,7 +1145,11 @@ static ssize_t store_debug_output(struct
 	return -EINVAL;
 }
 
-static DRIVER_ATTR(debug_output, S_IRUSR | S_IWUSR, show_debug_output, store_debug_output);
+#ifdef DRIVER_ATTR_RW
+static DRIVER_ATTR_RW(debug_output);
+#else
+static DRIVER_ATTR(debug_output, S_IRUSR | S_IWUSR, debug_output_show, debug_output_store);
+#endif
 #endif
 
 static struct platform_driver vhci_iocifc_driver = {
@@ -1069,7 +1166,9 @@ static void vhci_iocifc_device_release(s
 static int vhci_iocifc_major;
 
 static struct class vhci_iocifc_class = {
+#ifndef NO_CLASS_OWNER
 	.owner = THIS_MODULE,
+#endif
 	.name = driver_name
 };
 


--- vhci-hcd-1.15-old/usb-vhci-hcd.c	2025-03-19 11:59:09.392968891 +0530
+++ vhci-hcd-1.15-new/usb-vhci-hcd.c	2025-03-19 12:03:48.152972565 +0530
@@ -36,6 +36,7 @@
 #include <linux/usb.h>
 #include <linux/fs.h>
 #include <linux/device.h>
+#include <linux/version.h>
 
 #include <asm/atomic.h>
 #include <asm/bitops.h>
@@ -1295,7 +1296,11 @@ put_usb2_hcd:
 	return retval;
 }
 
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
+static void vhci_hcd_remove(struct platform_device *pdev)
+#else
 static int vhci_hcd_remove(struct platform_device *pdev)
+#endif
 {
 	unsigned long flags;
 	struct usb_vhci_hcd *vhc;
@@ -1351,8 +1356,11 @@ static int vhci_hcd_remove(struct platfo
 		vhci_dbg("call ifc->destroy\n");
 		vdev->ifc->destroy(vhcidev_to_ifc(vdev));
 	}
-
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
+	return;
+#else
 	return 0;
+#endif
 }
 
 static int vhci_hcd_suspend(struct platform_device *pdev, pm_message_t state)
@@ -1408,7 +1416,11 @@ static int vhci_hcd_resume(struct platfo
 
 static struct platform_driver vhci_hcd_driver = {
 	.probe      = vhci_hcd_probe,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(6,11,0)
+	.remove_new = vhci_hcd_remove,
+#else
 	.remove     = vhci_hcd_remove,
+#endif
 	.suspend    = vhci_hcd_suspend,
 	.resume     = vhci_hcd_resume,
 	.driver     = {
diff -uprN vhci-hcd-1.15.orig/test/Makefile vhci-hcd-1.15/test/Makefile
--- vhci-hcd-1.15.orig/test/Makefile	2013-06-21 05:32:38.000000000 +0530
+++ vhci-hcd-1.15/test/Makefile	2026-03-16 15:14:50.454737242 +0530
@@ -2,6 +2,7 @@ TARGET = test
 OBJS = test.o
 
 obj-m      := $(TARGET).o
+ccflags-y += $(EXTRA_CFLAGS)
 
 default:
 	make -C $(KDIR) SUBDIRS=$(PWD) PWD=$(PWD) BUILD_PREFIX=$(BUILD_PREFIX) KDIR=$(KDIR) KVERSION=$(KVERSION) EXTRA_CFLAGS='$(EXTRA_CFLAGS)' modules
vhci.patch (65,724 bytes)   

Relationships

related to 0001607 assignedtqhoang V4L2Loopback for Omnissa Horizon 8 Agent for Linux 

Activities

tqhoang

2026-09-21 16:53

administrator   ~0010969

Last edited: 2026-09-22 22:03

The patches were from Horizon 8 version 2603 with Agent for Linux 8.18.0.
Omnissa-horizonagent-linux-2603-8.18.0-24278394073.el.x86_64.rpm

https://customerconnect.omnissa.com/downloads/info/slug/virtual_desktop_and_apps/omnissa_horizon/2603

https://customerconnect.omnissa.com/downloads/details?downloadGroup=HZN-2603-LINUX&productId=1715&rPId=119330

tqhoang

2026-09-23 17:10

administrator   ~0010973

Last edited: 2026-09-23 17:11

The following packages have been pushed to testing:

kmod-omn-vhci-hcd-1.15-1.el9_8.elrepo.src.rpm
kmod-omn-vhci-hcd-1.15-1.el9_8.elrepo.x86_64.rpm

tqhoang

2026-10-02 09:52

administrator   ~0010985

The following packages have been pushed to testing:

kmod-omn-vhci-hcd-1.15-1.el8_10.elrepo.src.rpm
kmod-omn-vhci-hcd-1.15-1.el8_10.elrepo.x86_64.rpm

Issue History

Date Modified Username Field Change
2026-09-21 15:52 tqhoang New Issue
2026-09-21 15:52 tqhoang Status new => assigned
2026-09-21 15:52 tqhoang Assigned To => tqhoang
2026-09-21 15:52 tqhoang File Added: vhci.patch
2026-09-21 15:52 tqhoang Relationship added related to 0001607
2026-09-21 16:53 tqhoang Description Updated
2026-09-21 16:53 tqhoang Note Added: 0010969
2026-09-21 16:55 tqhoang Note Edited: 0010969
2026-09-22 22:03 tqhoang Note Edited: 0010969
2026-09-23 17:10 tqhoang Note Added: 0010973
2026-09-23 17:11 tqhoang Note Edited: 0010973
2026-09-29 16:23 tqhoang Category kmod-usbip => kmod-omn-vhci-hcd
2026-10-02 09:52 tqhoang Note Added: 0010985
2026-10-02 09:59 tqhoang Description Updated