If the xHC detects an oversubscription of bandwidth, it send a Bandwidth
Error completion code in the Command Completion event. The Get Port
Bandwidth command can be used to show the available bandwidth of each
root hub port of the xHC.

Signed-off-by: Michael Tretter <m.tret...@pengutronix.de>
---
 drivers/usb/host/xhci-ring.c | 15 ++++++++++++
 drivers/usb/host/xhci.c      | 55 ++++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/host/xhci.h      |  4 ++++
 3 files changed, 74 insertions(+)

diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index c5cbc685c691..1d9e07d69c9d 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -1430,6 +1430,8 @@ static void handle_cmd_completion(struct xhci_hcd *xhci,
                break;
        case TRB_EVAL_CONTEXT:
                break;
+       case TRB_GET_BW:
+               break;
        case TRB_ADDR_DEV:
                xhci_handle_cmd_addr_dev(xhci, slot_id);
                break;
@@ -4083,3 +4085,16 @@ int xhci_queue_reset_ep(struct xhci_hcd *xhci, struct 
xhci_command *cmd,
        return queue_command(xhci, cmd, 0, 0, 0,
                        trb_slot_id | trb_ep_index | type, false);
 }
+
+int xhci_queue_get_port_bandwidth(struct xhci_hcd *xhci,
+               struct xhci_command *cmd, dma_addr_t port_bw_ctx,
+               unsigned int hub_slot_id, unsigned int dev_speed)
+{
+       u32 trb_slot_id = SLOT_ID_FOR_TRB(hub_slot_id);
+       u32 trb_dev_speed = DEV_SPEED_FOR_TRB(dev_speed);
+
+       return queue_command(xhci, cmd, lower_32_bits(port_bw_ctx),
+                       upper_32_bits(port_bw_ctx), 0,
+                       trb_slot_id | trb_dev_speed | TRB_TYPE(TRB_GET_BW),
+                       false);
+}
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index da6dbe3ebd8b..8089885c9f25 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -1763,6 +1763,9 @@ static void xhci_zero_in_ctx(struct xhci_hcd *xhci, 
struct xhci_virt_device *vir
        }
 }
 
+static int xhci_get_port_bandwidth(struct xhci_hcd *xhci,
+                                  struct usb_device *udev);
+
 static int xhci_configure_endpoint_result(struct xhci_hcd *xhci,
                struct usb_device *udev, u32 *cmd_status)
 {
@@ -1786,6 +1789,7 @@ static int xhci_configure_endpoint_result(struct xhci_hcd 
*xhci,
                         "Not enough bandwidth for new device state.\n");
                ret = -ENOSPC;
                /* FIXME: can we go back to the old state? */
+               xhci_get_port_bandwidth(xhci, udev);
                break;
        case COMP_TRB_ERROR:
                /* the HCD set up something wrong */
@@ -2652,6 +2656,57 @@ static void xhci_check_bw_drop_ep_streams(struct 
xhci_hcd *xhci,
        }
 }
 
+static int xhci_get_port_bandwidth(struct xhci_hcd *xhci,
+                                  struct usb_device *udev)
+{
+       struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
+       struct xhci_command *command;
+       dma_addr_t dma;
+       unsigned int i;
+       char *ctx;
+       unsigned int hub_slot_id = 0;
+       unsigned int dev_speed =
+               udev->speed == USB_SPEED_HIGH ? 0x3 :
+               udev->speed == USB_SPEED_SUPER ? 0x4 :
+               0x0;
+       unsigned long flags;
+       int ret = 0;
+       unsigned int max_ports;
+
+       command = xhci_alloc_command(xhci, false, true, GFP_KERNEL);
+       if (!command)
+               return -ENOMEM;
+
+       max_ports = HCS_MAX_PORTS(xhci->hcs_params1) + 1;
+       ctx = dma_alloc_coherent(dev, round_up(max_ports, 8), &dma, GFP_KERNEL);
+       if (!ctx)
+               return -ENOMEM;
+
+       spin_lock_irqsave(&xhci->lock, flags);
+       ret = xhci_queue_get_port_bandwidth(xhci, command, dma,
+                                           hub_slot_id, dev_speed);
+       if (ret) {
+               spin_unlock_irqrestore(&xhci->lock, flags);
+               goto out;
+       }
+
+       xhci_ring_cmd_db(xhci);
+       spin_unlock_irqrestore(&xhci->lock, flags);
+
+       wait_for_completion(command->completion);
+
+       /* Read Bandwidth utilization from Context */
+       for (i = 0; i < max_ports; i++)
+               dev_warn(&udev->dev,
+                        "port %d - available bandwidth: %hhd %%\n", i, ctx[i]);
+
+out:
+       dma_free_coherent(dev, round_up(max_ports, 8), ctx, dma);
+       xhci_free_command(xhci, command);
+
+       return 0;
+}
+
 /* Called after one or more calls to xhci_add_endpoint() or
  * xhci_drop_endpoint().  If this call fails, the USB core is expected
  * to call xhci_reset_bandwidth().
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 99a014a920d3..62510a100135 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1219,6 +1219,7 @@ enum xhci_ep_reset_type {
 
 /* Get Port Bandwidth */
 #define TRB_TO_DEV_SPEED(p)            (((p) & (0xf << 16)) >> 16)
+#define DEV_SPEED_FOR_TRB(p)           (((p) & 0xf) << 16)
 
 /* Force Header */
 #define TRB_TO_PACKET_TYPE(p)          ((p) & 0x1f)
@@ -2053,6 +2054,9 @@ int xhci_queue_evaluate_context(struct xhci_hcd *xhci, 
struct xhci_command *cmd,
 int xhci_queue_reset_ep(struct xhci_hcd *xhci, struct xhci_command *cmd,
                int slot_id, unsigned int ep_index,
                enum xhci_ep_reset_type reset_type);
+int xhci_queue_get_port_bandwidth(struct xhci_hcd *xhci,
+               struct xhci_command *cmd, dma_addr_t port_bw_ctx,
+               unsigned int hub_slot_id, unsigned int dev_speed);
 int xhci_queue_reset_device(struct xhci_hcd *xhci, struct xhci_command *cmd,
                u32 slot_id);
 void xhci_find_new_dequeue_state(struct xhci_hcd *xhci,
-- 
2.11.0

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to