usb: xhci: Add debugfs support for xHCI port bandwidth
authorXu Rao <raoxu@uniontech.com>
Thu, 15 May 2025 13:55:59 +0000 (16:55 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 21 May 2025 10:35:31 +0000 (12:35 +0200)
In many projects, you need to obtain the available bandwidth of the
xhci roothub port. Refer to xhci rev1_2 and use the TRB_GET_BW
command to obtain it.

hardware tested:
03:00.3 USB controller: Advanced Micro Devices, Inc. [AMD] Raven USB 3.1
(prog-if 30 [XHCI])
Subsystem: Huawei Technologies Co., Ltd. Raven USB 3.1
Flags: bus master, fast devsel, latency 0, IRQ 30
Memory at c0300000 (64-bit, non-prefetchable) [size=1M]
Capabilities: [48] Vendor Specific Information: Len=08 <?>
Capabilities: [50] Power Management version 3
Capabilities: [64] Express Endpoint, MSI 00
Capabilities: [a0] MSI: Enable- Count=1/8 Maskable- 64bit+
Capabilities: [c0] MSI-X: Enable+ Count=8 Masked-
Kernel driver in use: xhci_hcd

test progress:
1. cd /sys/kernel/debug/usb/xhci/0000:03:00.3/port_bandwidth# ls
FS_BW  HS_BW  SS_BW
2. test fs speed  device
cat FS_BW
port[1] available bw: 90%.
port[2] available bw: 90%.
port[3] available bw: 90%.
port[4] available bw: 90%.
port[5] available bw: 0%.
port[6] available bw: 0%.
port[7] available bw: 0%.
port[8] available bw: 0%.
plug in fs usb audio ID 0d8c:013c
cat FS_BW
port[1] available bw: 76%.
port[2] available bw: 76%.
port[3] available bw: 76%.
port[4] available bw: 76%.
port[5] available bw: 0%.
port[6] available bw: 0%.
port[7] available bw: 0%.
port[8] available bw: 0%.
3. test hs speed device
cat HS_BW
port[1] available bw: 79%.
port[2] available bw: 79%.
port[3] available bw: 79%.
port[4] available bw: 79%.
port[5] available bw: 0%.
port[6] available bw: 0%.
port[7] available bw: 0%.
port[8] available bw: 0%.
plug in hs usb video ID 0408:1040
cat HS_BW
port[1] available bw: 39%.
port[2] available bw: 39%.
port[3] available bw: 39%.
port[4] available bw: 39%.
port[5] available bw: 0%.
port[6] available bw: 0%.
port[7] available bw: 0%.
port[8] available bw: 0%.
4.cat SS_BW
port[1] available bw: 0%.
port[2] available bw: 0%.
port[3] available bw: 0%.
port[4] available bw: 0%.
port[5] available bw: 90%.
port[6] available bw: 90%.
port[7] available bw: 90%.
port[8] available bw: 90%.

Signed-off-by: Xu Rao <raoxu@uniontech.com>
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://lore.kernel.org/r/20250515135621.335595-3-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/xhci-debugfs.c
drivers/usb/host/xhci-mem.c
drivers/usb/host/xhci-ring.c
drivers/usb/host/xhci.c
drivers/usb/host/xhci.h

index 1f5ef174abeaf03d9080a285562a2eb36860e140..c6d44977193f02d75137361c4224ac97ffc7ce34 100644 (file)
@@ -631,6 +631,112 @@ static void xhci_debugfs_create_ports(struct xhci_hcd *xhci,
        }
 }
 
+static int xhci_port_bw_show(struct xhci_hcd *xhci, u8 dev_speed,
+                               struct seq_file *s)
+{
+       unsigned int                    num_ports;
+       unsigned int                    i;
+       int                             ret;
+       struct xhci_container_ctx       *ctx;
+       struct usb_hcd                  *hcd = xhci_to_hcd(xhci);
+       struct device                   *dev = hcd->self.controller;
+
+       ret = pm_runtime_get_sync(dev);
+       if (ret < 0)
+               return ret;
+
+       num_ports = HCS_MAX_PORTS(xhci->hcs_params1);
+
+       ctx = xhci_alloc_port_bw_ctx(xhci, 0);
+       if (!ctx) {
+               pm_runtime_put_sync(dev);
+               return -ENOMEM;
+       }
+
+       /* get roothub port bandwidth */
+       ret = xhci_get_port_bandwidth(xhci, ctx, dev_speed);
+       if (ret)
+               goto err_out;
+
+       /* print all roothub ports available bandwidth
+        * refer to xhci rev1_2 protocol 6.2.6 , byte 0 is reserved
+        */
+       for (i = 1; i < num_ports+1; i++)
+               seq_printf(s, "port[%d] available bw: %d%%.\n", i,
+                               ctx->bytes[i]);
+err_out:
+       pm_runtime_put_sync(dev);
+       xhci_free_port_bw_ctx(xhci, ctx);
+       return ret;
+}
+
+static int xhci_ss_bw_show(struct seq_file *s, void *unused)
+{
+       int ret;
+       struct xhci_hcd         *xhci = (struct xhci_hcd *)s->private;
+
+       ret = xhci_port_bw_show(xhci, USB_SPEED_SUPER, s);
+       return ret;
+}
+
+static int xhci_hs_bw_show(struct seq_file *s, void *unused)
+{
+       int ret;
+       struct xhci_hcd         *xhci = (struct xhci_hcd *)s->private;
+
+       ret = xhci_port_bw_show(xhci, USB_SPEED_HIGH, s);
+       return ret;
+}
+
+static int xhci_fs_bw_show(struct seq_file *s, void *unused)
+{
+       int ret;
+       struct xhci_hcd         *xhci = (struct xhci_hcd *)s->private;
+
+       ret = xhci_port_bw_show(xhci, USB_SPEED_FULL, s);
+       return ret;
+}
+
+static struct xhci_file_map bw_context_files[] = {
+       {"SS_BW",       xhci_ss_bw_show, },
+       {"HS_BW",       xhci_hs_bw_show, },
+       {"FS_BW",       xhci_fs_bw_show, },
+};
+
+static int bw_context_open(struct inode *inode, struct file *file)
+{
+       int                     i;
+       struct xhci_file_map    *f_map;
+       const char              *file_name = file_dentry(file)->d_iname;
+
+       for (i = 0; i < ARRAY_SIZE(bw_context_files); i++) {
+               f_map = &bw_context_files[i];
+
+               if (strcmp(f_map->name, file_name) == 0)
+                       break;
+       }
+
+       return single_open(file, f_map->show, inode->i_private);
+}
+
+static const struct file_operations bw_fops = {
+       .open                   = bw_context_open,
+       .read                   = seq_read,
+       .llseek                 = seq_lseek,
+       .release                = single_release,
+};
+
+static void xhci_debugfs_create_bandwidth(struct xhci_hcd *xhci,
+                                       struct dentry *parent)
+{
+       parent = debugfs_create_dir("port_bandwidth", parent);
+
+       xhci_debugfs_create_files(xhci, bw_context_files,
+                         ARRAY_SIZE(bw_context_files),
+                         xhci,
+                         parent, &bw_fops);
+}
+
 void xhci_debugfs_init(struct xhci_hcd *xhci)
 {
        struct device           *dev = xhci_to_hcd(xhci)->self.controller;
@@ -681,6 +787,8 @@ void xhci_debugfs_init(struct xhci_hcd *xhci)
        xhci->debugfs_slots = debugfs_create_dir("devices", xhci->debugfs_root);
 
        xhci_debugfs_create_ports(xhci, xhci->debugfs_root);
+
+       xhci_debugfs_create_bandwidth(xhci, xhci->debugfs_root);
 }
 
 void xhci_debugfs_exit(struct xhci_hcd *xhci)
index ed36df46b140c767ad331f17c0574f14de0bc269..7ff6a47d3198c8347034ffe37db1d93c1ee034d5 100644 (file)
@@ -484,6 +484,35 @@ void xhci_free_container_ctx(struct xhci_hcd *xhci,
        kfree(ctx);
 }
 
+struct xhci_container_ctx *xhci_alloc_port_bw_ctx(struct xhci_hcd *xhci,
+                                                 gfp_t flags)
+{
+       struct xhci_container_ctx *ctx;
+       struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
+
+       ctx = kzalloc_node(sizeof(*ctx), flags, dev_to_node(dev));
+       if (!ctx)
+               return NULL;
+
+       ctx->size = GET_PORT_BW_ARRAY_SIZE;
+
+       ctx->bytes = dma_pool_zalloc(xhci->port_bw_pool, flags, &ctx->dma);
+       if (!ctx->bytes) {
+               kfree(ctx);
+               return NULL;
+       }
+       return ctx;
+}
+
+void xhci_free_port_bw_ctx(struct xhci_hcd *xhci,
+                            struct xhci_container_ctx *ctx)
+{
+       if (!ctx)
+               return;
+       dma_pool_free(xhci->port_bw_pool, ctx->bytes, ctx->dma);
+       kfree(ctx);
+}
+
 struct xhci_input_control_ctx *xhci_get_input_control_ctx(
                                              struct xhci_container_ctx *ctx)
 {
@@ -1912,6 +1941,11 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
        xhci_dbg_trace(xhci, trace_xhci_dbg_init,
                        "Freed small stream array pool");
 
+       dma_pool_destroy(xhci->port_bw_pool);
+       xhci->port_bw_pool = NULL;
+       xhci_dbg_trace(xhci, trace_xhci_dbg_init,
+                       "Freed xhci port bw array pool");
+
        dma_pool_destroy(xhci->medium_streams_pool);
        xhci->medium_streams_pool = NULL;
        xhci_dbg_trace(xhci, trace_xhci_dbg_init,
@@ -2475,7 +2509,16 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
         * will be allocated with dma_alloc_coherent()
         */
 
-       if (!xhci->small_streams_pool || !xhci->medium_streams_pool)
+       /* refer to xhci rev1_2 protocol 5.3.3 max ports is 255.
+        * refer to xhci rev1_2 protocol 6.4.3.14 port bandwidth buffer need
+        * to be 16-byte aligned.
+        */
+       xhci->port_bw_pool =
+               dma_pool_create("xHCI 256 port bw ctx arrays",
+                       dev, GET_PORT_BW_ARRAY_SIZE, 16, 0);
+
+       if (!xhci->small_streams_pool || !xhci->medium_streams_pool ||
+               !xhci->port_bw_pool)
                goto fail;
 
        /* Set up the command ring to have one segments for now. */
index 1d52682953962b36e5530293ef2f7a4a31954372..d2002ecd27b004ba11ecf2832bf76a3a7ce39ea8 100644 (file)
@@ -1898,6 +1898,8 @@ static void handle_cmd_completion(struct xhci_hcd *xhci,
        case TRB_NEC_GET_FW:
                xhci_handle_cmd_nec_get_fw(xhci, event);
                break;
+       case TRB_GET_BW:
+               break;
        default:
                /* Skip over unknown commands on the event ring */
                xhci_info(xhci, "INFO unknown command type %d\n", cmd_type);
@@ -4444,6 +4446,17 @@ int xhci_queue_configure_endpoint(struct xhci_hcd *xhci,
                        command_must_succeed);
 }
 
+/* Queue a get root hub port bandwidth command TRB */
+int xhci_queue_get_port_bw(struct xhci_hcd *xhci,
+               struct xhci_command *cmd, dma_addr_t in_ctx_ptr,
+               u8 dev_speed, bool command_must_succeed)
+{
+       return queue_command(xhci, cmd, lower_32_bits(in_ctx_ptr),
+               upper_32_bits(in_ctx_ptr), 0,
+               TRB_TYPE(TRB_GET_BW) | DEV_SPEED_FOR_TRB(dev_speed),
+               command_must_succeed);
+}
+
 /* Queue an evaluate context command TRB */
 int xhci_queue_evaluate_context(struct xhci_hcd *xhci, struct xhci_command *cmd,
                dma_addr_t in_ctx_ptr, u32 slot_id, bool command_must_succeed)
index b6267fc37b086562e42a0a4c05d6f14754ee81cb..fd9d41fe32248076f1c7bbafbea799958c65c91c 100644 (file)
@@ -3090,6 +3090,42 @@ void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev)
 }
 EXPORT_SYMBOL_GPL(xhci_reset_bandwidth);
 
+/* Get the available bandwidth of the ports under the xhci roothub */
+int xhci_get_port_bandwidth(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx,
+                           u8 dev_speed)
+{
+       struct xhci_command *cmd;
+       unsigned long flags;
+       int ret;
+
+       if (!ctx || !xhci)
+               return -EINVAL;
+
+       cmd = xhci_alloc_command(xhci, true, GFP_KERNEL);
+       if (!cmd)
+               return -ENOMEM;
+
+       cmd->in_ctx = ctx;
+
+       /* get xhci port bandwidth, refer to xhci rev1_2 protocol 4.6.15 */
+       spin_lock_irqsave(&xhci->lock, flags);
+
+       ret = xhci_queue_get_port_bw(xhci, cmd, ctx->dma, dev_speed, 0);
+       if (ret) {
+               spin_unlock_irqrestore(&xhci->lock, flags);
+               goto err_out;
+       }
+       xhci_ring_cmd_db(xhci);
+       spin_unlock_irqrestore(&xhci->lock, flags);
+
+       wait_for_completion(cmd->completion);
+err_out:
+       kfree(cmd->completion);
+       kfree(cmd);
+
+       return ret;
+}
+
 static void xhci_setup_input_ctx_for_config_ep(struct xhci_hcd *xhci,
                struct xhci_container_ctx *in_ctx,
                struct xhci_container_ctx *out_ctx,
index df9ed8a74af69653c685b2d9abe5e81b9ac841ec..f8198ec029812e8f92856ec32f877498b2c19480 100644 (file)
@@ -589,6 +589,7 @@ struct xhci_stream_info {
 
 #define        SMALL_STREAM_ARRAY_SIZE         256
 #define        MEDIUM_STREAM_ARRAY_SIZE        1024
+#define        GET_PORT_BW_ARRAY_SIZE          256
 
 /* Some Intel xHCI host controllers need software to keep track of the bus
  * bandwidth.  Keep track of endpoint info here.  Each root port is allocated
@@ -1006,6 +1007,9 @@ enum xhci_setup_dev {
 /* bits 16:23 are the virtual function ID */
 /* bits 24:31 are the slot ID */
 
+/* bits 19:16 are the dev speed */
+#define DEV_SPEED_FOR_TRB(p)    ((p) << 16)
+
 /* Stop Endpoint TRB - ep_index to endpoint ID for this TRB */
 #define SUSPEND_PORT_FOR_TRB(p)                (((p) & 1) << 23)
 #define TRB_TO_SUSPEND_PORT(p)         (((p) & (1 << 23)) >> 23)
@@ -1558,6 +1562,7 @@ struct xhci_hcd {
        struct dma_pool *device_pool;
        struct dma_pool *segment_pool;
        struct dma_pool *small_streams_pool;
+       struct dma_pool *port_bw_pool;
        struct dma_pool *medium_streams_pool;
 
        /* Host controller watchdog timer structures */
@@ -1850,6 +1855,10 @@ struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci,
                int type, gfp_t flags);
 void xhci_free_container_ctx(struct xhci_hcd *xhci,
                struct xhci_container_ctx *ctx);
+struct xhci_container_ctx *xhci_alloc_port_bw_ctx(struct xhci_hcd *xhci,
+               gfp_t flags);
+void xhci_free_port_bw_ctx(struct xhci_hcd *xhci,
+               struct xhci_container_ctx *ctx);
 struct xhci_interrupter *
 xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs,
                                  u32 imod_interval, unsigned int intr_num);
@@ -1923,6 +1932,11 @@ int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags,
 int xhci_queue_configure_endpoint(struct xhci_hcd *xhci,
                struct xhci_command *cmd, dma_addr_t in_ctx_ptr, u32 slot_id,
                bool command_must_succeed);
+int xhci_queue_get_port_bw(struct xhci_hcd *xhci,
+               struct xhci_command *cmd, dma_addr_t in_ctx_ptr,
+               u8 dev_speed, bool command_must_succeed);
+int xhci_get_port_bandwidth(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx,
+               u8 dev_speed);
 int xhci_queue_evaluate_context(struct xhci_hcd *xhci, struct xhci_command *cmd,
                dma_addr_t in_ctx_ptr, u32 slot_id, bool command_must_succeed);
 int xhci_queue_reset_ep(struct xhci_hcd *xhci, struct xhci_command *cmd,