[PATCH] usb: xhci-mtk: fix unreleased bandwidth data

From: Ikjoon Jang
Date: Mon Nov 30 2020 - 00:39:07 EST


xhci-mtk has hooks on add_endpoint() and drop_endpoint() from xhci
to handle its own sw bandwidth managements and stores bandwidth data
into internal table every time add_endpoint() is called,
so when one endpoint's bandwidth allocation fails, all earlier
endpoints from same interface still remain at the table.

This patch adds two more hooks from check_bandwidth() and
reset_bandwidth(), so mtk-xhci can releases all remaining
allocations in reset_bandwidth().

Fixes: 0cbd4b34cda9 ("xhci: mediatek: support MTK xHCI host controller")
Signed-off-by: Ikjoon Jang <ikjn@xxxxxxxxxxxx>
---

drivers/usb/host/xhci-mtk-sch.c | 163 ++++++++++++++++++++------------
drivers/usb/host/xhci-mtk.h | 13 +++
drivers/usb/host/xhci.c | 9 ++
3 files changed, 123 insertions(+), 62 deletions(-)

diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c
index 45c54d56ecbd..6fdc7be29420 100644
--- a/drivers/usb/host/xhci-mtk-sch.c
+++ b/drivers/usb/host/xhci-mtk-sch.c
@@ -49,9 +49,11 @@ static int is_fs_or_ls(enum usb_device_speed speed)
* so the bandwidth domain array is organized as follow for simplification:
* SSport0-OUT, SSport0-IN, ..., SSportX-OUT, SSportX-IN, HSport0, ..., HSportY
*/
-static int get_bw_index(struct xhci_hcd *xhci, struct usb_device *udev,
- struct usb_host_endpoint *ep)
+static struct mu3h_sch_bw_info *get_bw_info(struct xhci_hcd_mtk *mtk,
+ struct usb_device *udev,
+ struct usb_host_endpoint *ep)
{
+ struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd);
struct xhci_virt_device *virt_dev;
int bw_index;

@@ -67,7 +69,7 @@ static int get_bw_index(struct xhci_hcd *xhci, struct usb_device *udev,
bw_index = virt_dev->real_port + xhci->usb3_rhub.num_ports - 1;
}

- return bw_index;
+ return &mtk->sch_array[bw_index];
}

static u32 get_esit(struct xhci_ep_ctx *ep_ctx)
@@ -172,7 +174,6 @@ static struct mu3h_sch_ep_info *create_sch_ep(struct usb_device *udev,
struct usb_host_endpoint *ep, struct xhci_ep_ctx *ep_ctx)
{
struct mu3h_sch_ep_info *sch_ep;
- struct mu3h_sch_tt *tt = NULL;
u32 len_bw_budget_table;
size_t mem_size;

@@ -190,15 +191,6 @@ static struct mu3h_sch_ep_info *create_sch_ep(struct usb_device *udev,
if (!sch_ep)
return ERR_PTR(-ENOMEM);

- if (is_fs_or_ls(udev->speed)) {
- tt = find_tt(udev);
- if (IS_ERR(tt)) {
- kfree(sch_ep);
- return ERR_PTR(-ENOMEM);
- }
- }
-
- sch_ep->sch_tt = tt;
sch_ep->ep = ep;

return sch_ep;
@@ -375,10 +367,10 @@ static void update_bus_bw(struct mu3h_sch_bw_info *sch_bw,
}
}

-static int check_sch_tt(struct usb_device *udev,
- struct mu3h_sch_ep_info *sch_ep, u32 offset)
+static int check_sch_tt(struct mu3h_sch_tt *tt,
+ struct mu3h_sch_ep_info *sch_ep,
+ u32 offset)
{
- struct mu3h_sch_tt *tt = sch_ep->sch_tt;
u32 extra_cs_count;
u32 fs_budget_start;
u32 start_ss, last_ss;
@@ -448,10 +440,9 @@ static int check_sch_tt(struct usb_device *udev,
return 0;
}

-static void update_sch_tt(struct usb_device *udev,
- struct mu3h_sch_ep_info *sch_ep)
+static void update_sch_tt(struct mu3h_sch_tt *tt,
+ struct mu3h_sch_ep_info *sch_ep)
{
- struct mu3h_sch_tt *tt = sch_ep->sch_tt;
u32 base, num_esit;
int i, j;

@@ -463,6 +454,7 @@ static void update_sch_tt(struct usb_device *udev,
}

list_add_tail(&sch_ep->tt_endpoint, &tt->ep_list);
+ sch_ep->sch_tt = tt;
}

static int check_sch_bw(struct usb_device *udev,
@@ -476,22 +468,28 @@ static int check_sch_bw(struct usb_device *udev,
u32 bw_boundary;
u32 min_num_budget;
u32 min_cs_count;
+ struct mu3h_sch_tt *tt = NULL;
bool tt_offset_ok = false;
int ret;

- esit = sch_ep->esit;
+ if (is_fs_or_ls(udev->speed)) {
+ tt = find_tt(udev);
+ if (IS_ERR(tt))
+ return -ENOMEM;
+ }

/*
* Search through all possible schedule microframes.
* and find a microframe where its worst bandwidth is minimum.
*/
+ esit = sch_ep->esit;
min_bw = ~0;
min_index = 0;
min_cs_count = sch_ep->cs_count;
min_num_budget = sch_ep->num_budget_microframes;
for (offset = 0; offset < esit; offset++) {
if (is_fs_or_ls(udev->speed)) {
- ret = check_sch_tt(udev, sch_ep, offset);
+ ret = check_sch_tt(tt, sch_ep, offset);
if (ret)
continue;
else
@@ -529,10 +527,11 @@ static int check_sch_bw(struct usb_device *udev,

if (is_fs_or_ls(udev->speed)) {
/* all offset for tt is not ok*/
- if (!tt_offset_ok)
+ if (!tt_offset_ok) {
+ drop_tt(udev);
return -ERANGE;
-
- update_sch_tt(udev, sch_ep);
+ }
+ update_sch_tt(tt, sch_ep);
}

/* update bus bandwidth info */
@@ -583,6 +582,8 @@ int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk)

mtk->sch_array = sch_array;

+ INIT_LIST_HEAD(&mtk->bw_ep_list_new);
+
return 0;
}
EXPORT_SYMBOL_GPL(xhci_mtk_sch_init);
@@ -597,18 +598,14 @@ int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
struct usb_host_endpoint *ep)
{
struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd);
- struct xhci_hcd *xhci;
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct xhci_ep_ctx *ep_ctx;
struct xhci_slot_ctx *slot_ctx;
struct xhci_virt_device *virt_dev;
- struct mu3h_sch_bw_info *sch_bw;
struct mu3h_sch_ep_info *sch_ep;
struct mu3h_sch_bw_info *sch_array;
unsigned int ep_index;
- int bw_index;
- int ret = 0;

- xhci = hcd_to_xhci(hcd);
virt_dev = xhci->devs[udev->slot_id];
ep_index = xhci_get_endpoint_index(&ep->desc);
slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx);
@@ -632,26 +629,14 @@ int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
return 0;
}

- bw_index = get_bw_index(xhci, udev, ep);
- sch_bw = &sch_array[bw_index];
-
sch_ep = create_sch_ep(udev, ep, ep_ctx);
+
if (IS_ERR_OR_NULL(sch_ep))
return -ENOMEM;

setup_sch_info(udev, ep_ctx, sch_ep);

- ret = check_sch_bw(udev, sch_bw, sch_ep);
- if (ret) {
- xhci_err(xhci, "Not enough bandwidth!\n");
- if (is_fs_or_ls(udev->speed))
- drop_tt(udev);
-
- kfree(sch_ep);
- return -ENOSPC;
- }
-
- list_add_tail(&sch_ep->endpoint, &sch_bw->bw_ep_list);
+ list_add_tail(&sch_ep->endpoint, &mtk->bw_ep_list_new);

ep_ctx->reserved[0] |= cpu_to_le32(EP_BPKTS(sch_ep->pkts)
| EP_BCSCOUNT(sch_ep->cs_count) | EP_BBM(sch_ep->burst_mode));
@@ -666,22 +651,17 @@ int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
}
EXPORT_SYMBOL_GPL(xhci_mtk_add_ep_quirk);

-void xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
- struct usb_host_endpoint *ep)
+static void xhci_mtk_drop_ep(struct xhci_hcd_mtk *mtk, struct usb_device *udev,
+ struct mu3h_sch_ep_info *sch_ep)
{
- struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd);
- struct xhci_hcd *xhci;
+ struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd);
struct xhci_slot_ctx *slot_ctx;
struct xhci_virt_device *virt_dev;
- struct mu3h_sch_bw_info *sch_array;
struct mu3h_sch_bw_info *sch_bw;
- struct mu3h_sch_ep_info *sch_ep;
- int bw_index;
+ struct usb_host_endpoint *ep = sch_ep->ep;

- xhci = hcd_to_xhci(hcd);
virt_dev = xhci->devs[udev->slot_id];
slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx);
- sch_array = mtk->sch_array;

xhci_dbg(xhci, "%s() type:%d, speed:%d, mpks:%d, dir:%d, ep:%p\n",
__func__, usb_endpoint_type(&ep->desc), udev->speed,
@@ -691,20 +671,79 @@ void xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
if (!need_bw_sch(ep, udev->speed, slot_ctx->tt_info & TT_SLOT))
return;

- bw_index = get_bw_index(xhci, udev, ep);
- sch_bw = &sch_array[bw_index];
+ sch_bw = get_bw_info(mtk, udev, ep);

- list_for_each_entry(sch_ep, &sch_bw->bw_ep_list, endpoint) {
+ update_bus_bw(sch_bw, sch_ep, 0);
+
+ list_del(&sch_ep->endpoint);
+
+ if (sch_ep->sch_tt) {
+ list_del(&sch_ep->tt_endpoint);
+ drop_tt(udev);
+ }
+ kfree(sch_ep);
+}
+
+void xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
+ struct usb_host_endpoint *ep)
+{
+ struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd);
+ struct mu3h_sch_bw_info *sch_bw;
+ struct mu3h_sch_ep_info *sch_ep, *tmp;
+
+ sch_bw = get_bw_info(mtk, udev, ep);
+
+ list_for_each_entry_safe(sch_ep, tmp, &sch_bw->bw_ep_list, endpoint) {
if (sch_ep->ep == ep) {
- update_bus_bw(sch_bw, sch_ep, 0);
- list_del(&sch_ep->endpoint);
- if (is_fs_or_ls(udev->speed)) {
- list_del(&sch_ep->tt_endpoint);
- drop_tt(udev);
- }
- kfree(sch_ep);
+ xhci_mtk_drop_ep(mtk, udev, sch_ep);
break;
}
}
}
EXPORT_SYMBOL_GPL(xhci_mtk_drop_ep_quirk);
+
+int xhci_mtk_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev)
+{
+ struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd);
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ struct mu3h_sch_ep_info *sch_ep, *tmp;
+
+ dev_dbg(&udev->dev, "%s\n", __func__);
+
+ list_for_each_entry(sch_ep, &mtk->bw_ep_list_new, endpoint) {
+ int ret;
+ struct mu3h_sch_bw_info *sch_bw;
+
+ sch_bw = get_bw_info(mtk, udev, sch_ep->ep);
+
+ ret = check_sch_bw(udev, sch_bw, sch_ep);
+ if (ret) {
+ xhci_err(xhci, "Not enough bandwidth!\n");
+ return ret;
+ }
+ }
+
+ list_for_each_entry_safe(sch_ep, tmp, &mtk->bw_ep_list_new, endpoint) {
+ struct mu3h_sch_bw_info *sch_bw;
+
+ sch_bw = get_bw_info(mtk, udev, sch_ep->ep);
+
+ list_move_tail(&sch_ep->endpoint, &sch_bw->bw_ep_list);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(xhci_mtk_check_bandwidth);
+
+void xhci_mtk_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev)
+{
+ struct xhci_hcd_mtk *mtk = hcd_to_mtk(hcd);
+ struct mu3h_sch_ep_info *sch_ep, *tmp;
+
+ dev_dbg(&udev->dev, "%s\n", __func__);
+
+ list_for_each_entry_safe(sch_ep, tmp, &mtk->bw_ep_list_new, endpoint) {
+ xhci_mtk_drop_ep(mtk, udev, sch_ep);
+ }
+}
+EXPORT_SYMBOL_GPL(xhci_mtk_reset_bandwidth);
diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h
index 5ac458b7d2e0..5ba25b62bf5b 100644
--- a/drivers/usb/host/xhci-mtk.h
+++ b/drivers/usb/host/xhci-mtk.h
@@ -130,6 +130,7 @@ struct mu3c_ippc_regs {
struct xhci_hcd_mtk {
struct device *dev;
struct usb_hcd *hcd;
+ struct list_head bw_ep_list_new;
struct mu3h_sch_bw_info *sch_array;
struct mu3c_ippc_regs __iomem *ippc_regs;
bool has_ippc;
@@ -166,6 +167,8 @@ int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
struct usb_host_endpoint *ep);
void xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev,
struct usb_host_endpoint *ep);
+int xhci_mtk_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev);
+void xhci_mtk_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev);

#else
static inline int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd,
@@ -179,6 +182,16 @@ static inline void xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd,
{
}

+static inline int xhci_mtk_check_bandwidth(struct usb_hcd *hcd,
+ struct usb_device *udev)
+{
+ return 0;
+}
+
+static inline void xhci_mtk_reset_bandwidth(struct usb_hcd *hcd,
+ struct usb_device *udev)
+{
+}
#endif

#endif /* _XHCI_MTK_H_ */
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 0e297378063b..38c3c753bdbf 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -2882,6 +2882,12 @@ static int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev)
xhci_dbg(xhci, "%s called for udev %p\n", __func__, udev);
virt_dev = xhci->devs[udev->slot_id];

+ if (xhci->quirks & XHCI_MTK_HOST) {
+ ret = xhci_mtk_check_bandwidth(hcd, udev);
+ if (ret < 0)
+ return ret;
+ }
+
command = xhci_alloc_command(xhci, true, GFP_KERNEL);
if (!command)
return -ENOMEM;
@@ -2970,6 +2976,9 @@ static void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev)
return;
xhci = hcd_to_xhci(hcd);

+ if (xhci->quirks & XHCI_MTK_HOST)
+ xhci_mtk_reset_bandwidth(hcd, udev);
+
xhci_dbg(xhci, "%s called for udev %p\n", __func__, udev);
virt_dev = xhci->devs[udev->slot_id];
/* Free any rings allocated for added endpoints */
--
2.29.2.454.gaff20da3a2-goog