diff mbox series

[v3,1/2] USB: core: add a memory pool to urb caching host-controller private data

Message ID 20250517083523.5917-1-00107082@163.com
State New
Headers show
Series [v3,1/2] USB: core: add a memory pool to urb caching host-controller private data | expand

Commit Message

David Wang May 17, 2025, 8:35 a.m. UTC
---
Changes since v2:
1. activat the pool only when the urb object is created via
usb_alloc_urb()
Thanks to Oliver Neukum <oneukum@suse.com>'s review.
---
URB objects have long lifecycle, an urb can be reused between
submit loops; The private data needed by some host controller
has very short lifecycle, the memory is alloced when enqueue, and
released when dequeue. For example, on a system with xhci, in
xhci_urb_enqueue:
Using a USB webcam would have ~250/s memory allocation;
Using a USB mic would have ~1K/s memory allocation;

High frequent allocations for host-controller private data can be
avoided if urb take over the ownership of memory, the memory then shares
the longer lifecycle with urb objects.

Add a mempool to urb for hcpriv usage, the mempool only holds one block
of memory and grows when larger size is requested.

The mempool is activated only when the URB object is created via
usb_alloc_urb() in case some drivers create a URB object by other
means and manage it lifecycle without corresponding usb_free_urb.

The performance difference with this change is insignificant when
system is under no memory pressure or under heavy memory pressure.
There could be a point inbetween where extra 1k/s memory alloction
would dominate the preformance, but very hard to pinpoint it.

Signed-off-by: David Wang <00107082@163.com>
---
 drivers/usb/core/urb.c | 45 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/usb.h    |  5 +++++
 2 files changed, 50 insertions(+)
diff mbox series

Patch

diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c
index 5e52a35486af..53117743150f 100644
--- a/drivers/usb/core/urb.c
+++ b/drivers/usb/core/urb.c
@@ -23,6 +23,8 @@  static void urb_destroy(struct kref *kref)
 
 	if (urb->transfer_flags & URB_FREE_BUFFER)
 		kfree(urb->transfer_buffer);
+	if (urb->hcpriv_mempool_activated)
+		kfree(urb->hcpriv_mempool);
 
 	kfree(urb);
 }
@@ -77,6 +79,8 @@  struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
 	if (!urb)
 		return NULL;
 	usb_init_urb(urb);
+	/* activate hcpriv mempool when urb is created via usb_alloc_urb */
+	urb->hcpriv_mempool_activated = true;
 	return urb;
 }
 EXPORT_SYMBOL_GPL(usb_alloc_urb);
@@ -1037,3 +1041,44 @@  int usb_anchor_empty(struct usb_anchor *anchor)
 
 EXPORT_SYMBOL_GPL(usb_anchor_empty);
 
+/**
+ * urb_hcpriv_mempool_zalloc - alloc memory from mempool for hcpriv
+ * @urb: pointer to URB being used
+ * @size: memory size requested by current host controller
+ * @mem_flags: the type of memory to allocate
+ *
+ * Return: NULL if out of memory, otherwise memory are zeroed
+ */
+void *urb_hcpriv_mempool_zalloc(struct urb *urb, size_t size, gfp_t mem_flags)
+{
+	if (!urb->hcpriv_mempool_activated)
+		return kzalloc(size, mem_flags);
+
+	if (urb->hcpriv_mempool_size < size) {
+		kfree(urb->hcpriv_mempool);
+		urb->hcpriv_mempool_size = size;
+		urb->hcpriv_mempool = kmalloc(size, mem_flags);
+	}
+	if (urb->hcpriv_mempool)
+		memset(urb->hcpriv_mempool, 0, size);
+	else
+		urb->hcpriv_mempool_size = 0;
+	return urb->hcpriv_mempool;
+}
+EXPORT_SYMBOL_GPL(urb_hcpriv_mempool_zalloc);
+
+/**
+ * urb_free_hcpriv - free hcpriv data if necessary
+ * @urb: pointer to URB being used
+ *
+ * If mempool is activated, private data's lifecycle
+ * is managed by urb object.
+ */
+void urb_free_hcpriv(struct urb *urb)
+{
+	if (!urb->hcpriv_mempool_activated) {
+		kfree(urb->hcpriv);
+		urb->hcpriv = NULL;
+	}
+}
+EXPORT_SYMBOL_GPL(urb_free_hcpriv);
diff --git a/include/linux/usb.h b/include/linux/usb.h
index b46738701f8d..27bc394b8141 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -1602,6 +1602,9 @@  struct urb {
 	struct kref kref;		/* reference count of the URB */
 	int unlinked;			/* unlink error code */
 	void *hcpriv;			/* private data for host controller */
+	void *hcpriv_mempool;           /* a single slot of cache for HCD's private data */
+	size_t hcpriv_mempool_size;     /* current size of the memory pool */
+	bool hcpriv_mempool_activated;  /* flag the mempool usage */
 	atomic_t use_count;		/* concurrent submissions counter */
 	atomic_t reject;		/* submissions will fail */
 
@@ -1790,6 +1793,8 @@  extern int usb_wait_anchor_empty_timeout(struct usb_anchor *anchor,
 extern struct urb *usb_get_from_anchor(struct usb_anchor *anchor);
 extern void usb_scuttle_anchored_urbs(struct usb_anchor *anchor);
 extern int usb_anchor_empty(struct usb_anchor *anchor);
+extern void *urb_hcpriv_mempool_zalloc(struct urb *urb, size_t size, gfp_t mem_flags);
+extern void urb_free_hcpriv(struct urb *urb);
 
 #define usb_unblock_urb	usb_unpoison_urb