From ce7d0008c2356626f69f37ef1afce8fbc83fe142 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Sat, 10 Jul 2021 02:13:10 -0700 Subject: usb: gadget: udc: core: Introduce check_config to verify USB configuration Some UDCs may have constraints on how many high bandwidth endpoints it can support in a certain configuration. This API allows for the composite driver to pass down the total number of endpoints to the UDC so it can verify it has the required resources to support the configuration. Signed-off-by: Wesley Cheng Link: https://lore.kernel.org/r/1625908395-5498-2-git-send-email-wcheng@codeaurora.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/core.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index b7f0b1ebaaa8..14fdf918ecfe 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -1003,6 +1003,25 @@ int usb_gadget_ep_match_desc(struct usb_gadget *gadget, } EXPORT_SYMBOL_GPL(usb_gadget_ep_match_desc); +/** + * usb_gadget_check_config - checks if the UDC can support the binded + * configuration + * @gadget: controller to check the USB configuration + * + * Ensure that a UDC is able to support the requested resources by a + * configuration, and that there are no resource limitations, such as + * internal memory allocated to all requested endpoints. + * + * Returns zero on success, else a negative errno. + */ +int usb_gadget_check_config(struct usb_gadget *gadget) +{ + if (gadget->ops->check_config) + return gadget->ops->check_config(gadget); + return 0; +} +EXPORT_SYMBOL_GPL(usb_gadget_check_config); + /* ------------------------------------------------------------------------- */ static void usb_gadget_state_work(struct work_struct *work) -- cgit v1.2.3 From 7adf9e3adc398e5d5b7af91e5fdfafa70e86dd36 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Sat, 10 Jul 2021 02:13:11 -0700 Subject: usb: gadget: configfs: Check USB configuration before adding Ensure that the USB gadget is able to support the configuration being added based on the number of endpoints required from all interfaces. This is for accounting for any bandwidth or space limitations. Signed-off-by: Wesley Cheng Link: https://lore.kernel.org/r/1625908395-5498-3-git-send-email-wcheng@codeaurora.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/configfs.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 15a607ccef8a..f4c7c8241fe2 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -1404,6 +1404,10 @@ static int configfs_composite_bind(struct usb_gadget *gadget, goto err_purge_funcs; } } + ret = usb_gadget_check_config(cdev->gadget); + if (ret) + goto err_purge_funcs; + usb_ep_autoconfig_reset(cdev->gadget); } if (cdev->use_os_string) { -- cgit v1.2.3 From 9f607a309fbe95fc1f77acce5af70766a7142537 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Sat, 10 Jul 2021 02:13:12 -0700 Subject: usb: dwc3: Resize TX FIFOs to meet EP bursting requirements Some devices have USB compositions which may require multiple endpoints that support EP bursting. HW defined TX FIFO sizes may not always be sufficient for these compositions. By utilizing flexible TX FIFO allocation, this allows for endpoints to request the required FIFO depth to achieve higher bandwidth. With some higher bMaxBurst configurations, using a larger TX FIFO size results in better TX throughput. By introducing the check_config() callback, the resizing logic can fetch the maximum number of endpoints used in the USB composition (can contain multiple configurations), which helps ensure that the resizing logic can fulfill the configuration(s), or return an error to the gadget layer otherwise during bind time. Signed-off-by: Wesley Cheng Link: https://lore.kernel.org/r/1625908395-5498-4-git-send-email-wcheng@codeaurora.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 15 +++ drivers/usb/dwc3/core.h | 16 ++++ drivers/usb/dwc3/ep0.c | 2 + drivers/usb/dwc3/gadget.c | 232 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 265 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index ba74ad7f6995..b194aecd2202 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1267,6 +1267,7 @@ static void dwc3_get_properties(struct dwc3 *dwc) u8 rx_max_burst_prd; u8 tx_thr_num_pkt_prd; u8 tx_max_burst_prd; + u8 tx_fifo_resize_max_num; const char *usb_psy_name; int ret; @@ -1282,6 +1283,13 @@ static void dwc3_get_properties(struct dwc3 *dwc) */ hird_threshold = 12; + /* + * default to a TXFIFO size large enough to fit 6 max packets. This + * allows for systems with larger bus latencies to have some headroom + * for endpoints that have a large bMaxBurst value. + */ + tx_fifo_resize_max_num = 6; + dwc->maximum_speed = usb_get_maximum_speed(dev); dwc->max_ssp_rate = usb_get_maximum_ssp_rate(dev); dwc->dr_mode = usb_get_dr_mode(dev); @@ -1325,6 +1333,11 @@ static void dwc3_get_properties(struct dwc3 *dwc) &tx_thr_num_pkt_prd); device_property_read_u8(dev, "snps,tx-max-burst-prd", &tx_max_burst_prd); + dwc->do_fifo_resize = device_property_read_bool(dev, + "tx-fifo-resize"); + if (dwc->do_fifo_resize) + device_property_read_u8(dev, "tx-fifo-max-num", + &tx_fifo_resize_max_num); dwc->disable_scramble_quirk = device_property_read_bool(dev, "snps,disable_scramble_quirk"); @@ -1390,6 +1403,8 @@ static void dwc3_get_properties(struct dwc3 *dwc) dwc->tx_max_burst_prd = tx_max_burst_prd; dwc->imod_interval = 0; + + dwc->tx_fifo_resize_max_num = tx_fifo_resize_max_num; } /* check whether the core supports IMOD */ diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index dccdf13b5f9e..735e9be2a8df 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1023,6 +1023,7 @@ struct dwc3_scratchpad_array { * @rx_max_burst_prd: max periodic ESS receive burst size * @tx_thr_num_pkt_prd: periodic ESS transmit packet count * @tx_max_burst_prd: max periodic ESS transmit burst size + * @tx_fifo_resize_max_num: max number of fifos allocated during txfifo resize * @hsphy_interface: "utmi" or "ulpi" * @connected: true when we're connected to a host, false otherwise * @delayed_status: true when gadget driver asks for delayed status @@ -1037,6 +1038,7 @@ struct dwc3_scratchpad_array { * 1 - utmi_l1_suspend_n * @is_fpga: true when we are using the FPGA board * @pending_events: true when we have pending IRQs to be handled + * @do_fifo_resize: true when txfifo resizing is enabled for dwc3 endpoints * @pullups_connected: true when Run/Stop bit is set * @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround * @three_stage_setup: set if we perform a three phase setup @@ -1079,6 +1081,11 @@ struct dwc3_scratchpad_array { * @dis_split_quirk: set to disable split boundary. * @imod_interval: set the interrupt moderation interval in 250ns * increments or 0 to disable. + * @max_cfg_eps: current max number of IN eps used across all USB configs. + * @last_fifo_depth: last fifo depth used to determine next fifo ram start + * address. + * @num_ep_resized: carries the current number endpoints which have had its tx + * fifo resized. */ struct dwc3 { struct work_struct drd_work; @@ -1233,6 +1240,7 @@ struct dwc3 { u8 rx_max_burst_prd; u8 tx_thr_num_pkt_prd; u8 tx_max_burst_prd; + u8 tx_fifo_resize_max_num; const char *hsphy_interface; @@ -1246,6 +1254,7 @@ struct dwc3 { unsigned is_utmi_l1_suspend:1; unsigned is_fpga:1; unsigned pending_events:1; + unsigned do_fifo_resize:1; unsigned pullups_connected:1; unsigned setup_packet_pending:1; unsigned three_stage_setup:1; @@ -1281,6 +1290,10 @@ struct dwc3 { unsigned dis_split_quirk:1; u16 imod_interval; + + int max_cfg_eps; + int last_fifo_depth; + int num_ep_resized; }; #define INCRX_BURST_MODE 0 @@ -1512,6 +1525,7 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd, struct dwc3_gadget_ep_cmd_params *params); int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned int cmd, u32 param); +void dwc3_gadget_clear_tx_fifos(struct dwc3 *dwc); #else static inline int dwc3_gadget_init(struct dwc3 *dwc) { return 0; } @@ -1531,6 +1545,8 @@ static inline int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd, static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc, int cmd, u32 param) { return 0; } +static inline void dwc3_gadget_clear_tx_fifos(struct dwc3 *dwc) +{ } #endif #if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 3cd294264372..d28d08532455 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -619,6 +619,8 @@ static int dwc3_ep0_set_config(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) return -EINVAL; case USB_STATE_ADDRESS: + dwc3_gadget_clear_tx_fifos(dwc); + ret = dwc3_ep0_delegate_req(dwc, ctrl); /* if the cfg matches and the cfg is non zero */ if (cfg && (!ret || (ret == USB_GADGET_DELAYED_STATUS))) { diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index af6d7f157989..e56f1a6db2de 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -631,6 +631,187 @@ static int dwc3_gadget_set_ep_config(struct dwc3_ep *dep, unsigned int action) static void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force, bool interrupt); +/** + * dwc3_gadget_calc_tx_fifo_size - calculates the txfifo size value + * @dwc: pointer to the DWC3 context + * @nfifos: number of fifos to calculate for + * + * Calculates the size value based on the equation below: + * + * DWC3 revision 280A and prior: + * fifo_size = mult * (max_packet / mdwidth) + 1; + * + * DWC3 revision 290A and onwards: + * fifo_size = mult * ((max_packet + mdwidth)/mdwidth + 1) + 1 + * + * The max packet size is set to 1024, as the txfifo requirements mainly apply + * to super speed USB use cases. However, it is safe to overestimate the fifo + * allocations for other scenarios, i.e. high speed USB. + */ +static int dwc3_gadget_calc_tx_fifo_size(struct dwc3 *dwc, int mult) +{ + int max_packet = 1024; + int fifo_size; + int mdwidth; + + mdwidth = dwc3_mdwidth(dwc); + + /* MDWIDTH is represented in bits, we need it in bytes */ + mdwidth >>= 3; + + if (DWC3_VER_IS_PRIOR(DWC3, 290A)) + fifo_size = mult * (max_packet / mdwidth) + 1; + else + fifo_size = mult * ((max_packet + mdwidth) / mdwidth) + 1; + return fifo_size; +} + +/** + * dwc3_gadget_clear_tx_fifo_size - Clears txfifo allocation + * @dwc: pointer to the DWC3 context + * + * Iterates through all the endpoint registers and clears the previous txfifo + * allocations. + */ +void dwc3_gadget_clear_tx_fifos(struct dwc3 *dwc) +{ + struct dwc3_ep *dep; + int fifo_depth; + int size; + int num; + + if (!dwc->do_fifo_resize) + return; + + /* Read ep0IN related TXFIFO size */ + dep = dwc->eps[1]; + size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(0)); + if (DWC3_IP_IS(DWC3)) + fifo_depth = DWC3_GTXFIFOSIZ_TXFDEP(size); + else + fifo_depth = DWC31_GTXFIFOSIZ_TXFDEP(size); + + dwc->last_fifo_depth = fifo_depth; + /* Clear existing TXFIFO for all IN eps except ep0 */ + for (num = 3; num < min_t(int, dwc->num_eps, DWC3_ENDPOINTS_NUM); + num += 2) { + dep = dwc->eps[num]; + /* Don't change TXFRAMNUM on usb31 version */ + size = DWC3_IP_IS(DWC3) ? 0 : + dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(num >> 1)) & + DWC31_GTXFIFOSIZ_TXFRAMNUM; + + dwc3_writel(dwc->regs, DWC3_GTXFIFOSIZ(num >> 1), size); + } + dwc->num_ep_resized = 0; +} + +/* + * dwc3_gadget_resize_tx_fifos - reallocate fifo spaces for current use-case + * @dwc: pointer to our context structure + * + * This function will a best effort FIFO allocation in order + * to improve FIFO usage and throughput, while still allowing + * us to enable as many endpoints as possible. + * + * Keep in mind that this operation will be highly dependent + * on the configured size for RAM1 - which contains TxFifo -, + * the amount of endpoints enabled on coreConsultant tool, and + * the width of the Master Bus. + * + * In general, FIFO depths are represented with the following equation: + * + * fifo_size = mult * ((max_packet + mdwidth)/mdwidth + 1) + 1 + * + * In conjunction with dwc3_gadget_check_config(), this resizing logic will + * ensure that all endpoints will have enough internal memory for one max + * packet per endpoint. + */ +static int dwc3_gadget_resize_tx_fifos(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + int fifo_0_start; + int ram1_depth; + int fifo_size; + int min_depth; + int num_in_ep; + int remaining; + int num_fifos = 1; + int fifo; + int tmp; + + if (!dwc->do_fifo_resize) + return 0; + + /* resize IN endpoints except ep0 */ + if (!usb_endpoint_dir_in(dep->endpoint.desc) || dep->number <= 1) + return 0; + + ram1_depth = DWC3_RAM1_DEPTH(dwc->hwparams.hwparams7); + + if ((dep->endpoint.maxburst > 1 && + usb_endpoint_xfer_bulk(dep->endpoint.desc)) || + usb_endpoint_xfer_isoc(dep->endpoint.desc)) + num_fifos = 3; + + if (dep->endpoint.maxburst > 6 && + usb_endpoint_xfer_bulk(dep->endpoint.desc) && DWC3_IP_IS(DWC31)) + num_fifos = dwc->tx_fifo_resize_max_num; + + /* FIFO size for a single buffer */ + fifo = dwc3_gadget_calc_tx_fifo_size(dwc, 1); + + /* Calculate the number of remaining EPs w/o any FIFO */ + num_in_ep = dwc->max_cfg_eps; + num_in_ep -= dwc->num_ep_resized; + + /* Reserve at least one FIFO for the number of IN EPs */ + min_depth = num_in_ep * (fifo + 1); + remaining = ram1_depth - min_depth - dwc->last_fifo_depth; + remaining = max_t(int, 0, remaining); + /* + * We've already reserved 1 FIFO per EP, so check what we can fit in + * addition to it. If there is not enough remaining space, allocate + * all the remaining space to the EP. + */ + fifo_size = (num_fifos - 1) * fifo; + if (remaining < fifo_size) + fifo_size = remaining; + + fifo_size += fifo; + /* Last increment according to the TX FIFO size equation */ + fifo_size++; + + /* Check if TXFIFOs start at non-zero addr */ + tmp = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(0)); + fifo_0_start = DWC3_GTXFIFOSIZ_TXFSTADDR(tmp); + + fifo_size |= (fifo_0_start + (dwc->last_fifo_depth << 16)); + if (DWC3_IP_IS(DWC3)) + dwc->last_fifo_depth += DWC3_GTXFIFOSIZ_TXFDEP(fifo_size); + else + dwc->last_fifo_depth += DWC31_GTXFIFOSIZ_TXFDEP(fifo_size); + + /* Check fifo size allocation doesn't exceed available RAM size. */ + if (dwc->last_fifo_depth >= ram1_depth) { + dev_err(dwc->dev, "Fifosize(%d) > RAM size(%d) %s depth:%d\n", + dwc->last_fifo_depth, ram1_depth, + dep->endpoint.name, fifo_size); + if (DWC3_IP_IS(DWC3)) + fifo_size = DWC3_GTXFIFOSIZ_TXFDEP(fifo_size); + else + fifo_size = DWC31_GTXFIFOSIZ_TXFDEP(fifo_size); + + dwc->last_fifo_depth -= fifo_size; + return -ENOMEM; + } + + dwc3_writel(dwc->regs, DWC3_GTXFIFOSIZ(dep->number >> 1), fifo_size); + dwc->num_ep_resized++; + + return 0; +} + /** * __dwc3_gadget_ep_enable - initializes a hw endpoint * @dep: endpoint to be initialized @@ -648,6 +829,10 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action) int ret; if (!(dep->flags & DWC3_EP_ENABLED)) { + ret = dwc3_gadget_resize_tx_fifos(dep); + if (ret) + return ret; + ret = dwc3_gadget_start_config(dep); if (ret) return ret; @@ -2498,6 +2683,7 @@ static int dwc3_gadget_stop(struct usb_gadget *g) spin_lock_irqsave(&dwc->lock, flags); dwc->gadget_driver = NULL; + dwc->max_cfg_eps = 0; spin_unlock_irqrestore(&dwc->lock, flags); free_irq(dwc->irq_gadget, dwc->ev_buf); @@ -2585,6 +2771,51 @@ static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned int mA) return ret; } +/** + * dwc3_gadget_check_config - ensure dwc3 can support the USB configuration + * @g: pointer to the USB gadget + * + * Used to record the maximum number of endpoints being used in a USB composite + * device. (across all configurations) This is to be used in the calculation + * of the TXFIFO sizes when resizing internal memory for individual endpoints. + * It will help ensured that the resizing logic reserves enough space for at + * least one max packet. + */ +static int dwc3_gadget_check_config(struct usb_gadget *g) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + struct usb_ep *ep; + int fifo_size = 0; + int ram1_depth; + int ep_num = 0; + + if (!dwc->do_fifo_resize) + return 0; + + list_for_each_entry(ep, &g->ep_list, ep_list) { + /* Only interested in the IN endpoints */ + if (ep->claimed && (ep->address & USB_DIR_IN)) + ep_num++; + } + + if (ep_num <= dwc->max_cfg_eps) + return 0; + + /* Update the max number of eps in the composition */ + dwc->max_cfg_eps = ep_num; + + fifo_size = dwc3_gadget_calc_tx_fifo_size(dwc, dwc->max_cfg_eps); + /* Based on the equation, increment by one for every ep */ + fifo_size += dwc->max_cfg_eps; + + /* Check if we can fit a single fifo per endpoint */ + ram1_depth = DWC3_RAM1_DEPTH(dwc->hwparams.hwparams7); + if (fifo_size > ram1_depth) + return -ENOMEM; + + return 0; +} + static const struct usb_gadget_ops dwc3_gadget_ops = { .get_frame = dwc3_gadget_get_frame, .wakeup = dwc3_gadget_wakeup, @@ -2596,6 +2827,7 @@ static const struct usb_gadget_ops dwc3_gadget_ops = { .udc_set_ssp_rate = dwc3_gadget_set_ssp_rate, .get_config_params = dwc3_gadget_config_params, .vbus_draw = dwc3_gadget_vbus_draw, + .check_config = dwc3_gadget_check_config, }; /* -------------------------------------------------------------------------- */ -- cgit v1.2.3 From fe794e39548308e77e570fdf645d516554b3f873 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Sat, 10 Jul 2021 02:13:13 -0700 Subject: of: Add stub for of_add_property() If building with OF Kconfig disabled, this can lead to errors for drivers utilizing of_add_property(). Add a stub for the add API, as it exists for the remove variant as well, and to avoid compliation issues. Also, export this API so that it can be used by modules. Signed-off-by: Wesley Cheng Link: https://lore.kernel.org/r/1625908395-5498-5-git-send-email-wcheng@codeaurora.org Signed-off-by: Greg Kroah-Hartman --- drivers/of/base.c | 1 + include/linux/of.h | 5 +++++ 2 files changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/of/base.c b/drivers/of/base.c index 48e941f99558..5883d63c7714 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -1821,6 +1821,7 @@ int of_add_property(struct device_node *np, struct property *prop) return rc; } +EXPORT_SYMBOL_GPL(of_add_property); int __of_remove_property(struct device_node *np, struct property *prop) { diff --git a/include/linux/of.h b/include/linux/of.h index 9c2e71e202d1..0e786b60bd5d 100644 --- a/include/linux/of.h +++ b/include/linux/of.h @@ -946,6 +946,11 @@ static inline int of_machine_is_compatible(const char *compat) return 0; } +static inline int of_add_property(struct device_node *np, struct property *prop) +{ + return 0; +} + static inline int of_remove_property(struct device_node *np, struct property *prop) { return 0; -- cgit v1.2.3 From cefdd52fa0455c0555c30927386ee466a108b060 Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Sat, 10 Jul 2021 02:13:14 -0700 Subject: usb: dwc3: dwc3-qcom: Enable tx-fifo-resize property by default In order to take advantage of the TX fifo resizing logic, manually add these properties to the DWC3 child node by default. This will allow the DWC3 gadget to resize the TX fifos for the IN endpoints, which help with performance. Signed-off-by: Wesley Cheng Link: https://lore.kernel.org/r/1625908395-5498-6-git-send-email-wcheng@codeaurora.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 49e6ca94486d..2223b59e278f 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -645,6 +645,7 @@ static int dwc3_qcom_of_register_core(struct platform_device *pdev) struct dwc3_qcom *qcom = platform_get_drvdata(pdev); struct device_node *np = pdev->dev.of_node, *dwc3_np; struct device *dev = &pdev->dev; + struct property *prop; int ret; dwc3_np = of_get_compatible_child(np, "snps,dwc3"); @@ -653,6 +654,20 @@ static int dwc3_qcom_of_register_core(struct platform_device *pdev) return -ENODEV; } + prop = devm_kzalloc(dev, sizeof(*prop), GFP_KERNEL); + if (!prop) { + ret = -ENOMEM; + dev_err(dev, "unable to allocate memory for property\n"); + goto node_put; + } + + prop->name = "tx-fifo-resize"; + ret = of_add_property(dwc3_np, prop); + if (ret) { + dev_err(dev, "unable to add property\n"); + goto node_put; + } + ret = of_platform_populate(np, NULL, NULL, dev); if (ret) { dev_err(dev, "failed to register dwc3 core - %d\n", ret); -- cgit v1.2.3 From c4c1faf8254885a52412b456c90d0acc9e94db22 Mon Sep 17 00:00:00 2001 From: Kelly Devilliv Date: Sun, 27 Jun 2021 20:57:45 +0800 Subject: Revert "usb: host: fotg210: Use dma_pool_zalloc" This reverts commit cb6a0db8fdc12433ed4281331de0c3923dc2807b for the same reason as commit 43b78f1155c868208a413082179251f5fba78153 in the ehci-hcd driver. Alan writes: What you can't see just from reading the patch is that in both cases (ehci->itd_pool and ehci->sitd_pool) there are two allocation paths -- the two branches of an "if" statement -- and only one of the paths calls dma_pool_[z]alloc. However, the memset is needed for both paths, and so it can't be eliminated. Given that it must be present, there's no advantage to calling dma_pool_zalloc rather than dma_pool_alloc. Signed-off-by: Kelly Devilliv Link: https://lore.kernel.org/r/20210627125747.127646-2-kelly.devilliv@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/fotg210-hcd.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/fotg210-hcd.c b/drivers/usb/host/fotg210-hcd.c index 05fb8d97cf02..7858e930446a 100644 --- a/drivers/usb/host/fotg210-hcd.c +++ b/drivers/usb/host/fotg210-hcd.c @@ -1858,9 +1858,11 @@ static struct fotg210_qh *fotg210_qh_alloc(struct fotg210_hcd *fotg210, qh = kzalloc(sizeof(*qh), GFP_ATOMIC); if (!qh) goto done; - qh->hw = dma_pool_zalloc(fotg210->qh_pool, flags, &dma); + qh->hw = (struct fotg210_qh_hw *) + dma_pool_alloc(fotg210->qh_pool, flags, &dma); if (!qh->hw) goto fail; + memset(qh->hw, 0, sizeof(*qh->hw)); qh->qh_dma = dma; INIT_LIST_HEAD(&qh->qtd_list); @@ -4112,7 +4114,7 @@ static int itd_urb_transaction(struct fotg210_iso_stream *stream, } else { alloc_itd: spin_unlock_irqrestore(&fotg210->lock, flags); - itd = dma_pool_zalloc(fotg210->itd_pool, mem_flags, + itd = dma_pool_alloc(fotg210->itd_pool, mem_flags, &itd_dma); spin_lock_irqsave(&fotg210->lock, flags); if (!itd) { @@ -4122,6 +4124,7 @@ alloc_itd: } } + memset(itd, 0, sizeof(*itd)); itd->itd_dma = itd_dma; list_add(&itd->itd_list, &sched->td_list); } -- cgit v1.2.3 From c2e898764245c852bc8ee4857613ba4f3a6d761d Mon Sep 17 00:00:00 2001 From: Kelly Devilliv Date: Sun, 27 Jun 2021 20:57:46 +0800 Subject: usb: host: fotg210: fix the endpoint's transactional opportunities calculation Now that usb_endpoint_maxp() only returns the lowest 11 bits from wMaxPacketSize, we should make use of the usb_endpoint_* helpers instead and remove the unnecessary max_packet()/hb_mult() macro. Signed-off-by: Kelly Devilliv Link: https://lore.kernel.org/r/20210627125747.127646-3-kelly.devilliv@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/fotg210-hcd.c | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/fotg210-hcd.c b/drivers/usb/host/fotg210-hcd.c index 7858e930446a..629b35384e85 100644 --- a/drivers/usb/host/fotg210-hcd.c +++ b/drivers/usb/host/fotg210-hcd.c @@ -2512,11 +2512,6 @@ retry_xacterr: return count; } -/* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */ -#define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03)) -/* ... and packet size, for any kind of endpoint descriptor */ -#define max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff) - /* reverse of qh_urb_transaction: free a list of TDs. * used for cleanup after errors, before HC sees an URB's TDs. */ @@ -2602,7 +2597,7 @@ static struct list_head *qh_urb_transaction(struct fotg210_hcd *fotg210, token |= (1 /* "in" */ << 8); /* else it's already initted to "out" pid (0 << 8) */ - maxpacket = max_packet(usb_maxpacket(urb->dev, urb->pipe, !is_input)); + maxpacket = usb_maxpacket(urb->dev, urb->pipe, !is_input); /* * buffer gets wrapped in one or more qtds; @@ -2716,9 +2711,11 @@ static struct fotg210_qh *qh_make(struct fotg210_hcd *fotg210, struct urb *urb, gfp_t flags) { struct fotg210_qh *qh = fotg210_qh_alloc(fotg210, flags); + struct usb_host_endpoint *ep; u32 info1 = 0, info2 = 0; int is_input, type; int maxp = 0; + int mult; struct usb_tt *tt = urb->dev->tt; struct fotg210_qh_hw *hw; @@ -2733,14 +2730,15 @@ static struct fotg210_qh *qh_make(struct fotg210_hcd *fotg210, struct urb *urb, is_input = usb_pipein(urb->pipe); type = usb_pipetype(urb->pipe); - maxp = usb_maxpacket(urb->dev, urb->pipe, !is_input); + ep = usb_pipe_endpoint(urb->dev, urb->pipe); + maxp = usb_endpoint_maxp(&ep->desc); + mult = usb_endpoint_maxp_mult(&ep->desc); /* 1024 byte maxpacket is a hardware ceiling. High bandwidth * acts like up to 3KB, but is built from smaller packets. */ - if (max_packet(maxp) > 1024) { - fotg210_dbg(fotg210, "bogus qh maxpacket %d\n", - max_packet(maxp)); + if (maxp > 1024) { + fotg210_dbg(fotg210, "bogus qh maxpacket %d\n", maxp); goto done; } @@ -2754,8 +2752,7 @@ static struct fotg210_qh *qh_make(struct fotg210_hcd *fotg210, struct urb *urb, */ if (type == PIPE_INTERRUPT) { qh->usecs = NS_TO_US(usb_calc_bus_time(USB_SPEED_HIGH, - is_input, 0, - hb_mult(maxp) * max_packet(maxp))); + is_input, 0, mult * maxp)); qh->start = NO_FRAME; if (urb->dev->speed == USB_SPEED_HIGH) { @@ -2792,7 +2789,7 @@ static struct fotg210_qh *qh_make(struct fotg210_hcd *fotg210, struct urb *urb, think_time = tt ? tt->think_time : 0; qh->tt_usecs = NS_TO_US(think_time + usb_calc_bus_time(urb->dev->speed, - is_input, 0, max_packet(maxp))); + is_input, 0, maxp)); qh->period = urb->interval; if (qh->period > fotg210->periodic_size) { qh->period = fotg210->periodic_size; @@ -2855,11 +2852,11 @@ static struct fotg210_qh *qh_make(struct fotg210_hcd *fotg210, struct urb *urb, * to help them do so. So now people expect to use * such nonconformant devices with Linux too; sigh. */ - info1 |= max_packet(maxp) << 16; + info1 |= maxp << 16; info2 |= (FOTG210_TUNE_MULT_HS << 30); } else { /* PIPE_INTERRUPT */ - info1 |= max_packet(maxp) << 16; - info2 |= hb_mult(maxp) << 30; + info1 |= maxp << 16; + info2 |= mult << 30; } break; default: @@ -3929,6 +3926,7 @@ static void iso_stream_init(struct fotg210_hcd *fotg210, int is_input; long bandwidth; unsigned multi; + struct usb_host_endpoint *ep; /* * this might be a "high bandwidth" highspeed endpoint, @@ -3936,14 +3934,14 @@ static void iso_stream_init(struct fotg210_hcd *fotg210, */ epnum = usb_pipeendpoint(pipe); is_input = usb_pipein(pipe) ? USB_DIR_IN : 0; - maxp = usb_maxpacket(dev, pipe, !is_input); + ep = usb_pipe_endpoint(dev, pipe); + maxp = usb_endpoint_maxp(&ep->desc); if (is_input) buf1 = (1 << 11); else buf1 = 0; - maxp = max_packet(maxp); - multi = hb_mult(maxp); + multi = usb_endpoint_maxp_mult(&ep->desc); buf1 |= maxp; maxp *= multi; -- cgit v1.2.3 From 091cb2f782f32ab68c6f5f326d7868683d3d4875 Mon Sep 17 00:00:00 2001 From: Kelly Devilliv Date: Sun, 27 Jun 2021 20:57:47 +0800 Subject: usb: host: fotg210: fix the actual_length of an iso packet We should acquire the actual_length of an iso packet from the iTD directly using FOTG210_ITD_LENGTH() macro. Signed-off-by: Kelly Devilliv Link: https://lore.kernel.org/r/20210627125747.127646-4-kelly.devilliv@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/fotg210-hcd.c | 5 ++--- drivers/usb/host/fotg210.h | 5 ----- 2 files changed, 2 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/fotg210-hcd.c b/drivers/usb/host/fotg210-hcd.c index 629b35384e85..4b02ace09f3d 100644 --- a/drivers/usb/host/fotg210-hcd.c +++ b/drivers/usb/host/fotg210-hcd.c @@ -4463,13 +4463,12 @@ static bool itd_complete(struct fotg210_hcd *fotg210, struct fotg210_itd *itd) /* HC need not update length with this error */ if (!(t & FOTG210_ISOC_BABBLE)) { - desc->actual_length = - fotg210_itdlen(urb, desc, t); + desc->actual_length = FOTG210_ITD_LENGTH(t); urb->actual_length += desc->actual_length; } } else if (likely((t & FOTG210_ISOC_ACTIVE) == 0)) { desc->status = 0; - desc->actual_length = fotg210_itdlen(urb, desc, t); + desc->actual_length = FOTG210_ITD_LENGTH(t); urb->actual_length += desc->actual_length; } else { /* URB was too late */ diff --git a/drivers/usb/host/fotg210.h b/drivers/usb/host/fotg210.h index 0a91061a0551..0781442b7a24 100644 --- a/drivers/usb/host/fotg210.h +++ b/drivers/usb/host/fotg210.h @@ -683,11 +683,6 @@ static inline unsigned fotg210_read_frame_index(struct fotg210_hcd *fotg210) return fotg210_readl(fotg210, &fotg210->regs->frame_index); } -#define fotg210_itdlen(urb, desc, t) ({ \ - usb_pipein((urb)->pipe) ? \ - (desc)->length - FOTG210_ITD_LENGTH(t) : \ - FOTG210_ITD_LENGTH(t); \ -}) /*-------------------------------------------------------------------------*/ #endif /* __LINUX_FOTG210_H */ -- cgit v1.2.3 From dbaaca9aa5ce36c07a01a157d203071f6332a2af Mon Sep 17 00:00:00 2001 From: Maciej Żenczykowski Date: Thu, 1 Jul 2021 04:48:29 -0700 Subject: usb: gadget: f_ncm: remove timer_force_tx field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is simply not needed. This field is equivalent to skb being NULL. Currently with the boolean set to true we call: ncm->netdev->netdev_ops->ndo_start_xmit(NULL, ncm->netdev); which calls u_ether's: eth_start_xmit(NULL, ...) which then calls: skb = dev->wrap(dev->port_usb, NULL); which calls back into f_ncm's: ncm_wrap_ntb(..., NULL) Cc: Brooke Basile Cc: "Bryan O'Donoghue" Cc: Felipe Balbi Cc: Greg Kroah-Hartman Cc: Lorenzo Colitti Signed-off-by: Maciej Żenczykowski Link: https://lore.kernel.org/r/20210701114834.884597-1-zenczykowski@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_ncm.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c index 855127249f24..afbe70bc8d6b 100644 --- a/drivers/usb/gadget/function/f_ncm.c +++ b/drivers/usb/gadget/function/f_ncm.c @@ -72,7 +72,6 @@ struct f_ncm { struct sk_buff *skb_tx_data; struct sk_buff *skb_tx_ndp; u16 ndp_dgram_count; - bool timer_force_tx; struct hrtimer task_timer; bool timer_stopping; }; @@ -1126,8 +1125,11 @@ static struct sk_buff *ncm_wrap_ntb(struct gether *port, dev_consume_skb_any(skb); skb = NULL; - } else if (ncm->skb_tx_data && ncm->timer_force_tx) { - /* If the tx was requested because of a timeout then send */ + } else if (ncm->skb_tx_data) { + /* If we get here ncm_wrap_ntb() was called with NULL skb, + * because eth_start_xmit() was called with NULL skb by + * ncm_tx_timeout() - hence, this is our signal to flush/send. + */ skb2 = package_for_tx(ncm); if (!skb2) goto err; @@ -1158,8 +1160,6 @@ static enum hrtimer_restart ncm_tx_timeout(struct hrtimer *data) /* Only send if data is available. */ if (!ncm->timer_stopping && ncm->skb_tx_data) { - ncm->timer_force_tx = true; - /* XXX This allowance of a NULL skb argument to ndo_start_xmit * XXX is not sane. The gadget layer should be redesigned so * XXX that the dev->wrap() invocations to build SKBs is transparent @@ -1167,8 +1167,6 @@ static enum hrtimer_restart ncm_tx_timeout(struct hrtimer *data) * XXX interface. */ ncm->netdev->netdev_ops->ndo_start_xmit(NULL, ncm->netdev); - - ncm->timer_force_tx = false; } return HRTIMER_NORESTART; } -- cgit v1.2.3 From cf4e2e880bde13af103d229be261e8f80c37921f Mon Sep 17 00:00:00 2001 From: Maciej Żenczykowski Date: Thu, 1 Jul 2021 04:48:30 -0700 Subject: usb: gadget: f_ncm: remove spurious boolean timer_stopping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is equivalent to ncm->netdev being NULL. Cc: Brooke Basile Cc: "Bryan O'Donoghue" Cc: Felipe Balbi Cc: Greg Kroah-Hartman Cc: Lorenzo Colitti Signed-off-by: Maciej Żenczykowski Link: https://lore.kernel.org/r/20210701114834.884597-2-zenczykowski@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_ncm.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c index afbe70bc8d6b..e45a938424a4 100644 --- a/drivers/usb/gadget/function/f_ncm.c +++ b/drivers/usb/gadget/function/f_ncm.c @@ -73,7 +73,6 @@ struct f_ncm { struct sk_buff *skb_tx_ndp; u16 ndp_dgram_count; struct hrtimer task_timer; - bool timer_stopping; }; static inline struct f_ncm *func_to_ncm(struct usb_function *f) @@ -889,7 +888,6 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (ncm->port.in_ep->enabled) { DBG(cdev, "reset ncm\n"); - ncm->timer_stopping = true; ncm->netdev = NULL; gether_disconnect(&ncm->port); ncm_reset_values(ncm); @@ -927,7 +925,6 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (IS_ERR(net)) return PTR_ERR(net); ncm->netdev = net; - ncm->timer_stopping = false; } spin_lock(&ncm->lock); @@ -1157,16 +1154,19 @@ err: static enum hrtimer_restart ncm_tx_timeout(struct hrtimer *data) { struct f_ncm *ncm = container_of(data, struct f_ncm, task_timer); + struct net_device *netdev = READ_ONCE(ncm->netdev); /* Only send if data is available. */ - if (!ncm->timer_stopping && ncm->skb_tx_data) { + if (netdev && ncm->skb_tx_data) { /* XXX This allowance of a NULL skb argument to ndo_start_xmit * XXX is not sane. The gadget layer should be redesigned so * XXX that the dev->wrap() invocations to build SKBs is transparent * XXX and performed in some way outside of the ndo_start_xmit * XXX interface. + * + * This will call directly into u_ether's eth_start_xmit() */ - ncm->netdev->netdev_ops->ndo_start_xmit(NULL, ncm->netdev); + netdev->netdev_ops->ndo_start_xmit(NULL, netdev); } return HRTIMER_NORESTART; } @@ -1355,7 +1355,6 @@ static void ncm_disable(struct usb_function *f) DBG(cdev, "ncm deactivated\n"); if (ncm->port.in_ep->enabled) { - ncm->timer_stopping = true; ncm->netdev = NULL; gether_disconnect(&ncm->port); } -- cgit v1.2.3 From ec017d6b60f8aa400987fe95cd839e9ba5dda41d Mon Sep 17 00:00:00 2001 From: Maciej Żenczykowski Date: Thu, 1 Jul 2021 04:48:31 -0700 Subject: usb: gadget: f_ncm: remove check for NULL skb_tx_data in timer function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This condition is already checked for in ncm_wrap_ntb(), except that that check is done with eth_dev->lock held (it is grabbed by eth_start_xmit). It's best to not be reaching into ncm struct without locks held. Cc: Brooke Basile Cc: "Bryan O'Donoghue" Cc: Felipe Balbi Cc: Greg Kroah-Hartman Cc: Lorenzo Colitti Signed-off-by: Maciej Żenczykowski Link: https://lore.kernel.org/r/20210701114834.884597-3-zenczykowski@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_ncm.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c index e45a938424a4..77f55b3c805a 100644 --- a/drivers/usb/gadget/function/f_ncm.c +++ b/drivers/usb/gadget/function/f_ncm.c @@ -1156,8 +1156,7 @@ static enum hrtimer_restart ncm_tx_timeout(struct hrtimer *data) struct f_ncm *ncm = container_of(data, struct f_ncm, task_timer); struct net_device *netdev = READ_ONCE(ncm->netdev); - /* Only send if data is available. */ - if (netdev && ncm->skb_tx_data) { + if (netdev) { /* XXX This allowance of a NULL skb argument to ndo_start_xmit * XXX is not sane. The gadget layer should be redesigned so * XXX that the dev->wrap() invocations to build SKBs is transparent -- cgit v1.2.3 From b88668fec959fec0e77196455ec82778d8065941 Mon Sep 17 00:00:00 2001 From: Maciej Żenczykowski Date: Thu, 1 Jul 2021 04:48:32 -0700 Subject: usb: gadget: f_ncm: remove spurious if statement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the current logic is: struct sk_buff *skb2 = NULL; ... if (!skb && !ncm->skb_tx_data) return NULL; if (skb) { ... } else if (ncm->skb_tx_data) ... } return skb2; Which means that first if statement is simply not needed. Cc: Brooke Basile Cc: "Bryan O'Donoghue" Cc: Felipe Balbi Cc: Greg Kroah-Hartman Cc: Lorenzo Colitti Signed-off-by: Maciej Żenczykowski Link: https://lore.kernel.org/r/20210701114834.884597-4-zenczykowski@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_ncm.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c index 77f55b3c805a..cab17ae4fa34 100644 --- a/drivers/usb/gadget/function/f_ncm.c +++ b/drivers/usb/gadget/function/f_ncm.c @@ -1025,9 +1025,6 @@ static struct sk_buff *ncm_wrap_ntb(struct gether *port, const int rem = le16_to_cpu(ntb_parameters.wNdpInPayloadRemainder); const int dgram_idx_len = 2 * 2 * opts->dgram_item_len; - if (!skb && !ncm->skb_tx_data) - return NULL; - if (skb) { /* Add the CRC if required up front */ if (ncm->is_crc) { -- cgit v1.2.3 From 6607d1a4c3c946d3420d33d1b561d7bee8f693c9 Mon Sep 17 00:00:00 2001 From: Maciej Żenczykowski Date: Thu, 1 Jul 2021 04:48:33 -0700 Subject: usb: gadget: f_ncm: ncm_wrap_ntb - move var definitions into if statement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since they're only used if there's an skb. Cc: Brooke Basile Cc: "Bryan O'Donoghue" Cc: Felipe Balbi Cc: Greg Kroah-Hartman Cc: Lorenzo Colitti Signed-off-by: Maciej Żenczykowski Link: https://lore.kernel.org/r/20210701114834.884597-5-zenczykowski@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_ncm.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c index cab17ae4fa34..dc8f078f918c 100644 --- a/drivers/usb/gadget/function/f_ncm.c +++ b/drivers/usb/gadget/function/f_ncm.c @@ -1013,19 +1013,20 @@ static struct sk_buff *ncm_wrap_ntb(struct gether *port, { struct f_ncm *ncm = func_to_ncm(&port->func); struct sk_buff *skb2 = NULL; - int ncb_len = 0; - __le16 *ntb_data; - __le16 *ntb_ndp; - int dgram_pad; - - unsigned max_size = ncm->port.fixed_in_len; - const struct ndp_parser_opts *opts = ncm->parser_opts; - const int ndp_align = le16_to_cpu(ntb_parameters.wNdpInAlignment); - const int div = le16_to_cpu(ntb_parameters.wNdpInDivisor); - const int rem = le16_to_cpu(ntb_parameters.wNdpInPayloadRemainder); - const int dgram_idx_len = 2 * 2 * opts->dgram_item_len; if (skb) { + int ncb_len = 0; + __le16 *ntb_data; + __le16 *ntb_ndp; + int dgram_pad; + + unsigned max_size = ncm->port.fixed_in_len; + const struct ndp_parser_opts *opts = ncm->parser_opts; + const int ndp_align = le16_to_cpu(ntb_parameters.wNdpInAlignment); + const int div = le16_to_cpu(ntb_parameters.wNdpInDivisor); + const int rem = le16_to_cpu(ntb_parameters.wNdpInPayloadRemainder); + const int dgram_idx_len = 2 * 2 * opts->dgram_item_len; + /* Add the CRC if required up front */ if (ncm->is_crc) { uint32_t crc; -- cgit v1.2.3 From 8ae01239609b29ec2eff55967c8e0fe3650cfa09 Mon Sep 17 00:00:00 2001 From: Maciej Żenczykowski Date: Thu, 1 Jul 2021 04:48:34 -0700 Subject: usb: gadget: u_ether: fix a potential null pointer dereference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit f_ncm tx timeout can call us with null skb to flush a pending frame. In this case skb is NULL to begin with but ceases to be null after dev->wrap() completes. In such a case in->maxpacket will be read, even though we've failed to check that 'in' is not NULL. Though I've never observed this fail in practice, however the 'flush operation' simply does not make sense with a null usb IN endpoint - there's nowhere to flush to... (note that we're the gadget/device, and IN is from the point of view of the host, so here IN actually means outbound...) Cc: Brooke Basile Cc: "Bryan O'Donoghue" Cc: Felipe Balbi Cc: Greg Kroah-Hartman Cc: Lorenzo Colitti Signed-off-by: Maciej Żenczykowski Link: https://lore.kernel.org/r/20210701114834.884597-6-zenczykowski@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/u_ether.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c index d1d044d9f859..85a3f6d4b5af 100644 --- a/drivers/usb/gadget/function/u_ether.c +++ b/drivers/usb/gadget/function/u_ether.c @@ -492,8 +492,9 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, } spin_unlock_irqrestore(&dev->lock, flags); - if (skb && !in) { - dev_kfree_skb_any(skb); + if (!in) { + if (skb) + dev_kfree_skb_any(skb); return NETDEV_TX_OK; } -- cgit v1.2.3 From 61136a12cbed234374ec6f588af57c580b20b772 Mon Sep 17 00:00:00 2001 From: Evgeny Novikov Date: Thu, 8 Jul 2021 11:30:56 +0300 Subject: USB: EHCI: ehci-mv: improve error handling in mv_ehci_enable() mv_ehci_enable() did not disable and unprepare clocks in case of failures of phy_init(). Besides, it did not take into account failures of ehci_clock_enable() (in effect, failures of clk_prepare_enable()). The patch fixes both issues and gets rid of redundant wrappers around clk_prepare_enable() and clk_disable_unprepare() to simplify this a bit. Found by Linux Driver Verification project (linuxtesting.org). Acked-by: Alan Stern Signed-off-by: Evgeny Novikov Link: https://lore.kernel.org/r/20210708083056.21543-1-novikov@ispras.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-mv.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-mv.c b/drivers/usb/host/ehci-mv.c index cffdc8d01b2a..8fd27249ad25 100644 --- a/drivers/usb/host/ehci-mv.c +++ b/drivers/usb/host/ehci-mv.c @@ -42,26 +42,25 @@ struct ehci_hcd_mv { int (*set_vbus)(unsigned int vbus); }; -static void ehci_clock_enable(struct ehci_hcd_mv *ehci_mv) +static int mv_ehci_enable(struct ehci_hcd_mv *ehci_mv) { - clk_prepare_enable(ehci_mv->clk); -} + int retval; -static void ehci_clock_disable(struct ehci_hcd_mv *ehci_mv) -{ - clk_disable_unprepare(ehci_mv->clk); -} + retval = clk_prepare_enable(ehci_mv->clk); + if (retval) + return retval; -static int mv_ehci_enable(struct ehci_hcd_mv *ehci_mv) -{ - ehci_clock_enable(ehci_mv); - return phy_init(ehci_mv->phy); + retval = phy_init(ehci_mv->phy); + if (retval) + clk_disable_unprepare(ehci_mv->clk); + + return retval; } static void mv_ehci_disable(struct ehci_hcd_mv *ehci_mv) { phy_exit(ehci_mv->phy); - ehci_clock_disable(ehci_mv); + clk_disable_unprepare(ehci_mv->clk); } static int mv_ehci_reset(struct usb_hcd *hcd) -- cgit v1.2.3 From e725ace06fc4072a5a9e934805fbe42454b32c90 Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Mon, 12 Jul 2021 21:16:07 +0300 Subject: usb: host: ohci-spear: simplify calling usb_add_hcd() There is no need to call platform_get_irq() when the driver's probe() method calls usb_add_hcd() -- the platform_get_irq()'s result will have been stored already in the 'irq' local variable... Acked-by: Alan Stern Signed-off-by: Sergey Shtylyov Link: https://lore.kernel.org/r/3e4ad969-f2ae-32f7-53fd-ea369f140703@omp.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-spear.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/ohci-spear.c b/drivers/usb/host/ohci-spear.c index 5cc05449281c..b4cd9e6c72fd 100644 --- a/drivers/usb/host/ohci-spear.c +++ b/drivers/usb/host/ohci-spear.c @@ -84,7 +84,7 @@ static int spear_ohci_hcd_drv_probe(struct platform_device *pdev) clk_prepare_enable(sohci_p->clk); - retval = usb_add_hcd(hcd, platform_get_irq(pdev, 0), 0); + retval = usb_add_hcd(hcd, irq, 0); if (retval == 0) { device_wakeup_enable(hcd->self.controller); return retval; -- cgit v1.2.3 From e13690d527bb400b09ef669d28bd11d8db3f1b08 Mon Sep 17 00:00:00 2001 From: Moritz Fischer Date: Sat, 17 Jul 2021 18:51:10 -0700 Subject: usb: xhci-renesas: Minor coding style cleanup Change an explicit err == 0 to !err. No functional change. Cc: Mathias Nyman Cc: Greg Kroah-Hartman Cc: Vinod Koul Signed-off-by: Moritz Fischer Link: https://lore.kernel.org/r/20210718015111.389719-2-mdf@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-pci-renesas.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-pci-renesas.c b/drivers/usb/host/xhci-pci-renesas.c index 1da647961c25..327f6a6d5672 100644 --- a/drivers/usb/host/xhci-pci-renesas.c +++ b/drivers/usb/host/xhci-pci-renesas.c @@ -595,7 +595,7 @@ int renesas_xhci_check_request_fw(struct pci_dev *pdev, err = renesas_fw_check_running(pdev); /* Continue ahead, if the firmware is already running. */ - if (err == 0) + if (!err) return 0; if (err != 1) -- cgit v1.2.3 From 884c274408296e7e0f56545f909b3d3a671104aa Mon Sep 17 00:00:00 2001 From: Moritz Fischer Date: Sat, 17 Jul 2021 18:51:11 -0700 Subject: usb: renesas-xhci: Remove renesas_xhci_pci_exit() Remove empty function renesas_xhci_pci_exit() that does not actually do anything. Cc: Mathias Nyman Cc: Greg Kroah-Hartman Cc: Vinod Koul Signed-off-by: Moritz Fischer Link: https://lore.kernel.org/r/20210718015111.389719-3-mdf@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-pci-renesas.c | 5 ----- drivers/usb/host/xhci-pci.c | 2 -- drivers/usb/host/xhci-pci.h | 3 --- 3 files changed, 10 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-pci-renesas.c b/drivers/usb/host/xhci-pci-renesas.c index 327f6a6d5672..1ebacc42a552 100644 --- a/drivers/usb/host/xhci-pci-renesas.c +++ b/drivers/usb/host/xhci-pci-renesas.c @@ -620,9 +620,4 @@ exit: } EXPORT_SYMBOL_GPL(renesas_xhci_check_request_fw); -void renesas_xhci_pci_exit(struct pci_dev *dev) -{ -} -EXPORT_SYMBOL_GPL(renesas_xhci_pci_exit); - MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 18c2bbddf080..4456ba338b74 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -449,8 +449,6 @@ static void xhci_pci_remove(struct pci_dev *dev) struct xhci_hcd *xhci; xhci = hcd_to_xhci(pci_get_drvdata(dev)); - if (xhci->quirks & XHCI_RENESAS_FW_QUIRK) - renesas_xhci_pci_exit(dev); xhci->xhc_state |= XHCI_STATE_REMOVING; diff --git a/drivers/usb/host/xhci-pci.h b/drivers/usb/host/xhci-pci.h index acd7cf0a1706..cb9a8f331a44 100644 --- a/drivers/usb/host/xhci-pci.h +++ b/drivers/usb/host/xhci-pci.h @@ -7,7 +7,6 @@ #if IS_ENABLED(CONFIG_USB_XHCI_PCI_RENESAS) int renesas_xhci_check_request_fw(struct pci_dev *dev, const struct pci_device_id *id); -void renesas_xhci_pci_exit(struct pci_dev *dev); #else static int renesas_xhci_check_request_fw(struct pci_dev *dev, @@ -16,8 +15,6 @@ static int renesas_xhci_check_request_fw(struct pci_dev *dev, return 0; } -static void renesas_xhci_pci_exit(struct pci_dev *dev) { }; - #endif struct xhci_driver_data { -- cgit v1.2.3 From fb4e52b609f0e29468b8f5b71117456410ff7d3b Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 20 Jul 2021 22:03:36 +0200 Subject: usb: isp1301-omap: Fix the GPIO include The file is using only GPIO descriptors, so only include instead of the legacy include. This is a non-urgent fix. Fixes: f3ef38160e3d ("usb: isp1301-omap: Convert to use GPIO descriptors") Cc: Tony Lindgren Cc: Felipe Balbi Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20210720200336.223398-1-linus.walleij@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-isp1301-omap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/phy/phy-isp1301-omap.c b/drivers/usb/phy/phy-isp1301-omap.c index f3e9b3b6ac3e..190699b38b41 100644 --- a/drivers/usb/phy/phy-isp1301-omap.c +++ b/drivers/usb/phy/phy-isp1301-omap.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include -- cgit v1.2.3 From bcacbf06c891374e7fdd7b72d11cda03b0269b43 Mon Sep 17 00:00:00 2001 From: Jack Pham Date: Tue, 20 Jul 2021 01:09:07 -0700 Subject: usb: gadget: composite: Allow bMaxPower=0 if self-powered Currently the composite driver encodes the MaxPower field of the configuration descriptor by reading the c->MaxPower of the usb_configuration only if it is non-zero, otherwise it falls back to using the value hard-coded in CONFIG_USB_GADGET_VBUS_DRAW. However, there are cases when a configuration must explicitly set bMaxPower to 0, particularly if its bmAttributes also has the Self-Powered bit set, which is a valid combination. This is specifically called out in the USB PD specification section 9.1, in which a PDUSB device "shall report zero in the bMaxPower field after negotiating a mutually agreeable Contract", and also verified by the USB Type-C Functional Test TD.4.10.2 Sink Power Precedence Test. The fix allows the c->MaxPower to be used for encoding the bMaxPower even if it is 0, if the self-powered bit is also set. An example usage of this would be for a ConfigFS gadget to be dynamically updated by userspace when the Type-C connection is determined to be operating in Power Delivery mode. Co-developed-by: Ronak Vijay Raheja Acked-by: Felipe Balbi Signed-off-by: Ronak Vijay Raheja Signed-off-by: Jack Pham Link: https://lore.kernel.org/r/20210720080907.30292-1-jackp@codeaurora.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/composite.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 72a9797dbbae..504c1cbc255d 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -482,7 +482,7 @@ static u8 encode_bMaxPower(enum usb_device_speed speed, { unsigned val; - if (c->MaxPower) + if (c->MaxPower || (c->bmAttributes & USB_CONFIG_ATT_SELFPOWER)) val = c->MaxPower; else val = CONFIG_USB_GADGET_VBUS_DRAW; @@ -936,7 +936,11 @@ static int set_config(struct usb_composite_dev *cdev, } /* when we return, be sure our power usage is valid */ - power = c->MaxPower ? c->MaxPower : CONFIG_USB_GADGET_VBUS_DRAW; + if (c->MaxPower || (c->bmAttributes & USB_CONFIG_ATT_SELFPOWER)) + power = c->MaxPower; + else + power = CONFIG_USB_GADGET_VBUS_DRAW; + if (gadget->speed < USB_SPEED_SUPER) power = min(power, 500U); else -- cgit v1.2.3 From b833ce15ce33a6e6b1d729fc78af79362aa8681e Mon Sep 17 00:00:00 2001 From: Minas Harutyunyan Date: Mon, 12 Jul 2021 15:05:23 +0400 Subject: usb: dwc2: gadget: Add endpoint wedge support Add enpoint wedge support. Tested by USBCV MSC tests. Signed-off-by: Argishti Aleksanyan Signed-off-by: Minas Harutyunyan Link: https://lore.kernel.org/r/3143ea6b8eee08761709a6c2788216292be46a34.1626087390.git.Minas.Harutyunyan@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/core.h | 2 ++ drivers/usb/dwc2/gadget.c | 28 +++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index ab6b815e0089..1f14bcc94b69 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -122,6 +122,7 @@ struct dwc2_hsotg_req; * @periodic: Set if this is a periodic ep, such as Interrupt * @isochronous: Set if this is a isochronous ep * @send_zlp: Set if we need to send a zero-length packet. + * @wedged: Set if ep is wedged. * @desc_list_dma: The DMA address of descriptor chain currently in use. * @desc_list: Pointer to descriptor DMA chain head currently in use. * @desc_count: Count of entries within the DMA descriptor chain of EP. @@ -172,6 +173,7 @@ struct dwc2_hsotg_ep { unsigned int periodic:1; unsigned int isochronous:1; unsigned int send_zlp:1; + unsigned int wedged:1; unsigned int target_frame; #define TARGET_FRAME_INITIAL 0xFFFFFFFF bool frame_overrun; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index c581ee41ac81..fe567069dcb2 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -1806,7 +1806,8 @@ static int dwc2_hsotg_process_req_feature(struct dwc2_hsotg *hsotg, case USB_ENDPOINT_HALT: halted = ep->halted; - dwc2_hsotg_ep_sethalt(&ep->ep, set, true); + if (!ep->wedged) + dwc2_hsotg_ep_sethalt(&ep->ep, set, true); ret = dwc2_hsotg_send_reply(hsotg, ep0, NULL, 0); if (ret) { @@ -4046,6 +4047,7 @@ static int dwc2_hsotg_ep_enable(struct usb_ep *ep, hs_ep->isochronous = 0; hs_ep->periodic = 0; hs_ep->halted = 0; + hs_ep->wedged = 0; hs_ep->interval = desc->bInterval; switch (ep_type) { @@ -4286,6 +4288,27 @@ static int dwc2_hsotg_ep_dequeue(struct usb_ep *ep, struct usb_request *req) return 0; } +/** + * dwc2_gadget_ep_set_wedge - set wedge on a given endpoint + * @ep: The endpoint to be wedged. + * + */ +static int dwc2_gadget_ep_set_wedge(struct usb_ep *ep) +{ + struct dwc2_hsotg_ep *hs_ep = our_ep(ep); + struct dwc2_hsotg *hs = hs_ep->parent; + + unsigned long flags; + int ret; + + spin_lock_irqsave(&hs->lock, flags); + hs_ep->wedged = 1; + ret = dwc2_hsotg_ep_sethalt(ep, 1, false); + spin_unlock_irqrestore(&hs->lock, flags); + + return ret; +} + /** * dwc2_hsotg_ep_sethalt - set halt on a given endpoint * @ep: The endpoint to set halt. @@ -4337,6 +4360,7 @@ static int dwc2_hsotg_ep_sethalt(struct usb_ep *ep, int value, bool now) epctl |= DXEPCTL_EPDIS; } else { epctl &= ~DXEPCTL_STALL; + hs_ep->wedged = 0; xfertype = epctl & DXEPCTL_EPTYPE_MASK; if (xfertype == DXEPCTL_EPTYPE_BULK || xfertype == DXEPCTL_EPTYPE_INTERRUPT) @@ -4353,6 +4377,7 @@ static int dwc2_hsotg_ep_sethalt(struct usb_ep *ep, int value, bool now) // STALL bit will be set in GOUTNAKEFF interrupt handler } else { epctl &= ~DXEPCTL_STALL; + hs_ep->wedged = 0; xfertype = epctl & DXEPCTL_EPTYPE_MASK; if (xfertype == DXEPCTL_EPTYPE_BULK || xfertype == DXEPCTL_EPTYPE_INTERRUPT) @@ -4392,6 +4417,7 @@ static const struct usb_ep_ops dwc2_hsotg_ep_ops = { .queue = dwc2_hsotg_ep_queue_lock, .dequeue = dwc2_hsotg_ep_dequeue, .set_halt = dwc2_hsotg_ep_sethalt_lock, + .set_wedge = dwc2_gadget_ep_set_wedge, /* note, don't believe we have any call for the fifo routines */ }; -- cgit v1.2.3 From 02de698ca8123782c0c6fb8ed99080e2f032b0d2 Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Mon, 12 Jul 2021 14:55:27 +0200 Subject: usb: gadget: u_audio: add bi-directional volume and mute support USB Audio Class 1/2 have ability to change device's volume and mute by USB Host through class-specific control requests. Device also can notify Host about volume/mute change on its side through optional interrupt endpoint. This patch adds Volume and Mute ALSA controls which can be used by user to send and receive notifications to/from the USB Host about Volume and Mute change. These params come from f_uac* so volume and mute controls will be created only if the function support and enable each explicitly Signed-off-by: Ruslan Bilovol Signed-off-by: Pavel Hofman Link: https://lore.kernel.org/r/20210712125529.76070-3-pavel.hofman@ivitera.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/u_audio.c | 369 +++++++++++++++++++++++++++++++++- drivers/usb/gadget/function/u_audio.h | 22 ++ 2 files changed, 380 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c index 018dd0978995..f6b5b9547236 100644 --- a/drivers/usb/gadget/function/u_audio.c +++ b/drivers/usb/gadget/function/u_audio.c @@ -12,11 +12,14 @@ * Jaswinder Singh (jaswinder.singh@linaro.org) */ +#include #include #include #include #include #include +#include +#include #include "u_audio.h" @@ -24,6 +27,12 @@ #define PRD_SIZE_MAX PAGE_SIZE #define MIN_PERIODS 4 +enum { + UAC_FBACK_CTRL, + UAC_MUTE_CTRL, + UAC_VOLUME_CTRL, +}; + /* Runtime data params for one stream */ struct uac_rtd_params { struct snd_uac_chip *uac; /* parent chip */ @@ -43,6 +52,17 @@ struct uac_rtd_params { struct usb_request *req_fback; /* Feedback endpoint request */ bool fb_ep_enabled; /* if the ep is enabled */ + + /* Volume/Mute controls and their state */ + int fu_id; /* Feature Unit ID */ + struct snd_kcontrol *snd_kctl_volume; + struct snd_kcontrol *snd_kctl_mute; + s16 volume_min, volume_max, volume_res; + s16 volume; + int mute; + + spinlock_t lock; /* lock for control transfers */ + }; struct snd_uac_chip { @@ -597,6 +617,103 @@ void u_audio_stop_playback(struct g_audio *audio_dev) } EXPORT_SYMBOL_GPL(u_audio_stop_playback); +int u_audio_get_volume(struct g_audio *audio_dev, int playback, s16 *val) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + unsigned long flags; + + if (playback) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + spin_lock_irqsave(&prm->lock, flags); + *val = prm->volume; + spin_unlock_irqrestore(&prm->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_get_volume); + +int u_audio_set_volume(struct g_audio *audio_dev, int playback, s16 val) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + unsigned long flags; + int change = 0; + + if (playback) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + spin_lock_irqsave(&prm->lock, flags); + val = clamp(val, prm->volume_min, prm->volume_max); + if (prm->volume != val) { + prm->volume = val; + change = 1; + } + spin_unlock_irqrestore(&prm->lock, flags); + + if (change) + snd_ctl_notify(uac->card, SNDRV_CTL_EVENT_MASK_VALUE, + &prm->snd_kctl_volume->id); + + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_set_volume); + +int u_audio_get_mute(struct g_audio *audio_dev, int playback, int *val) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + unsigned long flags; + + if (playback) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + spin_lock_irqsave(&prm->lock, flags); + *val = prm->mute; + spin_unlock_irqrestore(&prm->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_get_mute); + +int u_audio_set_mute(struct g_audio *audio_dev, int playback, int val) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + unsigned long flags; + int change = 0; + int mute; + + if (playback) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + mute = val ? 1 : 0; + + spin_lock_irqsave(&prm->lock, flags); + if (prm->mute != mute) { + prm->mute = mute; + change = 1; + } + spin_unlock_irqrestore(&prm->lock, flags); + + if (change) + snd_ctl_notify(uac->card, SNDRV_CTL_EVENT_MASK_VALUE, + &prm->snd_kctl_mute->id); + + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_set_mute); + + static int u_audio_pitch_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { @@ -656,14 +773,158 @@ static int u_audio_pitch_put(struct snd_kcontrol *kcontrol, return change; } -static const struct snd_kcontrol_new u_audio_controls[] = { +static int u_audio_mute_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) { - .iface = SNDRV_CTL_ELEM_IFACE_PCM, - .name = "Capture Pitch 1000000", - .info = u_audio_pitch_info, - .get = u_audio_pitch_get, - .put = u_audio_pitch_put, -}, + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + uinfo->value.integer.step = 1; + + return 0; +} + +static int u_audio_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&prm->lock, flags); + ucontrol->value.integer.value[0] = !prm->mute; + spin_unlock_irqrestore(&prm->lock, flags); + + return 0; +} + +static int u_audio_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev = uac->audio_dev; + unsigned int val; + unsigned long flags; + int change = 0; + + val = !ucontrol->value.integer.value[0]; + + spin_lock_irqsave(&prm->lock, flags); + if (val != prm->mute) { + prm->mute = val; + change = 1; + } + spin_unlock_irqrestore(&prm->lock, flags); + + if (change && audio_dev->notify) + audio_dev->notify(audio_dev, prm->fu_id, UAC_FU_MUTE); + + return change; +} + +/* + * TLV callback for mixer volume controls + */ +static int u_audio_volume_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *_tlv) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + DECLARE_TLV_DB_MINMAX(scale, 0, 0); + + if (size < sizeof(scale)) + return -ENOMEM; + + /* UAC volume resolution is 1/256 dB, TLV is 1/100 dB */ + scale[2] = (prm->volume_min * 100) / 256; + scale[3] = (prm->volume_max * 100) / 256; + if (copy_to_user(_tlv, scale, sizeof(scale))) + return -EFAULT; + + return 0; +} + +static int u_audio_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = + (prm->volume_max - prm->volume_min + prm->volume_res - 1) + / prm->volume_res; + uinfo->value.integer.step = 1; + + return 0; +} + +static int u_audio_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&prm->lock, flags); + ucontrol->value.integer.value[0] = + (prm->volume - prm->volume_min) / prm->volume_res; + spin_unlock_irqrestore(&prm->lock, flags); + + return 0; +} + +static int u_audio_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev = uac->audio_dev; + unsigned int val; + s16 volume; + unsigned long flags; + int change = 0; + + val = ucontrol->value.integer.value[0]; + + spin_lock_irqsave(&prm->lock, flags); + volume = (val * prm->volume_res) + prm->volume_min; + volume = clamp(volume, prm->volume_min, prm->volume_max); + if (volume != prm->volume) { + prm->volume = volume; + change = 1; + } + spin_unlock_irqrestore(&prm->lock, flags); + + if (change && audio_dev->notify) + audio_dev->notify(audio_dev, prm->fu_id, UAC_FU_VOLUME); + + return change; +} + + +static struct snd_kcontrol_new u_audio_controls[] = { + [UAC_FBACK_CTRL] { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Capture Pitch 1000000", + .info = u_audio_pitch_info, + .get = u_audio_pitch_get, + .put = u_audio_pitch_put, + }, + [UAC_MUTE_CTRL] { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", /* will be filled later */ + .info = u_audio_mute_info, + .get = u_audio_mute_get, + .put = u_audio_mute_put, + }, + [UAC_VOLUME_CTRL] { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", /* will be filled later */ + .info = u_audio_volume_info, + .get = u_audio_volume_get, + .put = u_audio_volume_put, + }, }; int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, @@ -675,7 +936,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, struct snd_kcontrol *kctl; struct uac_params *params; int p_chmask, c_chmask; - int err; + int i, err; if (!g_audio) return -EINVAL; @@ -693,7 +954,8 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, if (c_chmask) { struct uac_rtd_params *prm = &uac->c_prm; - uac->c_prm.uac = uac; + spin_lock_init(&prm->lock); + uac->c_prm.uac = uac; prm->max_psize = g_audio->out_ep_maxpsize; prm->reqs = kcalloc(params->req_number, @@ -716,6 +978,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, if (p_chmask) { struct uac_rtd_params *prm = &uac->p_prm; + spin_lock_init(&prm->lock); uac->p_prm.uac = uac; prm->max_psize = g_audio->in_ep_maxpsize; @@ -760,10 +1023,18 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac_pcm_ops); snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac_pcm_ops); - if (c_chmask && g_audio->in_ep_fback) { + /* + * Create mixer and controls + * Create only if it's required on USB side + */ + if ((c_chmask && g_audio->in_ep_fback) + || (p_chmask && params->p_fu.id) + || (c_chmask && params->c_fu.id)) strscpy(card->mixername, card_name, sizeof(card->driver)); - kctl = snd_ctl_new1(&u_audio_controls[0], &uac->c_prm); + if (c_chmask && g_audio->in_ep_fback) { + kctl = snd_ctl_new1(&u_audio_controls[UAC_FBACK_CTRL], + &uac->c_prm); if (!kctl) { err = -ENOMEM; goto snd_fail; @@ -777,6 +1048,82 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, goto snd_fail; } + for (i = 0; i <= SNDRV_PCM_STREAM_LAST; i++) { + struct uac_rtd_params *prm; + struct uac_fu_params *fu; + char ctrl_name[24]; + char *direction; + + if (!pcm->streams[i].substream_count) + continue; + + if (i == SNDRV_PCM_STREAM_PLAYBACK) { + prm = &uac->p_prm; + fu = ¶ms->p_fu; + direction = "Playback"; + } else { + prm = &uac->c_prm; + fu = ¶ms->c_fu; + direction = "Capture"; + } + + prm->fu_id = fu->id; + + if (fu->mute_present) { + snprintf(ctrl_name, sizeof(ctrl_name), + "PCM %s Switch", direction); + + u_audio_controls[UAC_MUTE_CTRL].name = ctrl_name; + + kctl = snd_ctl_new1(&u_audio_controls[UAC_MUTE_CTRL], + prm); + if (!kctl) { + err = -ENOMEM; + goto snd_fail; + } + + kctl->id.device = pcm->device; + kctl->id.subdevice = i; + + err = snd_ctl_add(card, kctl); + if (err < 0) + goto snd_fail; + prm->snd_kctl_mute = kctl; + prm->mute = 0; + } + + if (fu->volume_present) { + snprintf(ctrl_name, sizeof(ctrl_name), + "PCM %s Volume", direction); + + u_audio_controls[UAC_VOLUME_CTRL].name = ctrl_name; + + kctl = snd_ctl_new1(&u_audio_controls[UAC_VOLUME_CTRL], + prm); + if (!kctl) { + err = -ENOMEM; + goto snd_fail; + } + + kctl->id.device = pcm->device; + kctl->id.subdevice = i; + + + kctl->tlv.c = u_audio_volume_tlv; + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + + err = snd_ctl_add(card, kctl); + if (err < 0) + goto snd_fail; + prm->snd_kctl_volume = kctl; + prm->volume = fu->volume_max; + prm->volume_max = fu->volume_max; + prm->volume_min = fu->volume_min; + prm->volume_res = fu->volume_res; + } + } + strscpy(card->driver, card_name, sizeof(card->driver)); strscpy(card->shortname, card_name, sizeof(card->shortname)); sprintf(card->longname, "%s %i", card_name, card->dev->id); diff --git a/drivers/usb/gadget/function/u_audio.h b/drivers/usb/gadget/function/u_audio.h index a218cdf771fe..001a79a46022 100644 --- a/drivers/usb/gadget/function/u_audio.h +++ b/drivers/usb/gadget/function/u_audio.h @@ -19,16 +19,30 @@ */ #define FBACK_SLOW_MAX 250 +/* Feature Unit parameters */ +struct uac_fu_params { + int id; /* Feature Unit ID */ + + bool mute_present; /* mute control enable */ + + bool volume_present; /* volume control enable */ + s16 volume_min; /* min volume in 1/256 dB */ + s16 volume_max; /* max volume in 1/256 dB */ + s16 volume_res; /* volume resolution in 1/256 dB */ +}; + struct uac_params { /* playback */ int p_chmask; /* channel mask */ int p_srate; /* rate in Hz */ int p_ssize; /* sample size */ + struct uac_fu_params p_fu; /* Feature Unit parameters */ /* capture */ int c_chmask; /* channel mask */ int c_srate; /* rate in Hz */ int c_ssize; /* sample size */ + struct uac_fu_params c_fu; /* Feature Unit parameters */ int req_number; /* number of preallocated requests */ int fb_max; /* upper frequency drift feedback limit per-mil */ @@ -49,6 +63,9 @@ struct g_audio { /* Max packet size for all out_ep possible speeds */ unsigned int out_ep_maxpsize; + /* Notify UAC driver about control change */ + int (*notify)(struct g_audio *g_audio, int unit_id, int cs); + /* The ALSA Sound Card it represents on the USB-Client side */ struct snd_uac_chip *uac; @@ -94,4 +111,9 @@ void u_audio_stop_capture(struct g_audio *g_audio); int u_audio_start_playback(struct g_audio *g_audio); void u_audio_stop_playback(struct g_audio *g_audio); +int u_audio_get_volume(struct g_audio *g_audio, int playback, s16 *val); +int u_audio_set_volume(struct g_audio *g_audio, int playback, s16 val); +int u_audio_get_mute(struct g_audio *g_audio, int playback, int *val); +int u_audio_set_mute(struct g_audio *g_audio, int playback, int val); + #endif /* __U_AUDIO_H */ -- cgit v1.2.3 From eaf6cbe0992052a46d93047dc122fad5126aa3bd Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Mon, 12 Jul 2021 14:55:28 +0200 Subject: usb: gadget: f_uac2: add volume and mute support This adds bi-directional (host->device, device->host) volume/mute support to the f_uac2 driver by adding Feature Units and interrupt endpoint. Currently only master channel is supported. Volume and mute are configurable through configfs, by default volume has -100..0 dB range with 1 dB step. Similar to existing flexible endpoints configuration, Feature Unit won't be added to the topology if both mute and volume are not enabled, also interrupt endpoint isn't added to the device if no feature unit is present Signed-off-by: Ruslan Bilovol Signed-off-by: Pavel Hofman Link: https://lore.kernel.org/r/20210712125529.76070-4-pavel.hofman@ivitera.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/configfs-usb-gadget-uac2 | 10 + Documentation/usb/gadget-testing.rst | 12 +- drivers/usb/gadget/function/f_uac2.c | 656 +++++++++++++++++++-- drivers/usb/gadget/function/u_uac2.h | 23 +- 4 files changed, 638 insertions(+), 63 deletions(-) (limited to 'drivers') diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac2 b/Documentation/ABI/testing/configfs-usb-gadget-uac2 index 26fb8e9b4e61..cfd160ff8b56 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uac2 +++ b/Documentation/ABI/testing/configfs-usb-gadget-uac2 @@ -9,8 +9,18 @@ Description: c_srate capture sampling rate c_ssize capture sample size (bytes) c_sync capture synchronization type (async/adaptive) + c_mute_present capture mute control enable + c_volume_present capture volume control enable + c_volume_min capture volume control min value (in 1/256 dB) + c_volume_max capture volume control max value (in 1/256 dB) + c_volume_res capture volume control resolution (in 1/256 dB) fb_max maximum extra bandwidth in async mode p_chmask playback channel mask p_srate playback sampling rate p_ssize playback sample size (bytes) + p_mute_present playback mute control enable + p_volume_present playback volume control enable + p_volume_min playback volume control min value (in 1/256 dB) + p_volume_max playback volume control max value (in 1/256 dB) + p_volume_res playback volume control resolution (in 1/256 dB) ========= ============================ diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index 9d6276f82774..272e51f17f54 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -729,10 +729,20 @@ The uac2 function provides these attributes in its function directory: c_srate capture sampling rate c_ssize capture sample size (bytes) c_sync capture synchronization type (async/adaptive) - fb_max maximum extra bandwidth in async mode + c_mute_present capture mute control enable + c_volume_present capture volume control enable + c_volume_min capture volume control min value (in 1/256 dB) + c_volume_max capture volume control max value (in 1/256 dB) + c_volume_res capture volume control resolution (in 1/256 dB) + fb_max maximum extra bandwidth in async mode p_chmask playback channel mask p_srate playback sampling rate p_ssize playback sample size (bytes) + p_mute_present playback mute control enable + p_volume_present playback volume control enable + p_volume_min playback volume control min value (in 1/256 dB) + p_volume_max playback volume control max value (in 1/256 dB) + p_volume_res playback volume control resolution (in 1/256 dB) req_number the number of pre-allocated request for both capture and playback =============== ==================================================== diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index ae29ff2b2b68..b9edc6787f79 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -5,6 +5,9 @@ * Copyright (C) 2011 * Yadwinder Singh (yadi.brar01@gmail.com) * Jaswinder Singh (jaswinder.singh@linaro.org) + * + * Copyright (C) 2020 + * Ruslan Bilovol (ruslan.bilovol@gmail.com) */ #include @@ -19,14 +22,16 @@ /* * The driver implements a simple UAC_2 topology. - * USB-OUT -> IT_1 -> OT_3 -> ALSA_Capture - * ALSA_Playback -> IT_2 -> OT_4 -> USB-IN + * USB-OUT -> IT_1 -> FU -> OT_3 -> ALSA_Capture + * ALSA_Playback -> IT_2 -> FU -> OT_4 -> USB-IN * Capture and Playback sampling rates are independently * controlled by two clock sources : * CLK_5 := c_srate, and CLK_6 := p_srate */ #define USB_OUT_CLK_ID (out_clk_src_desc.bClockID) #define USB_IN_CLK_ID (in_clk_src_desc.bClockID) +#define USB_OUT_FU_ID (out_feature_unit_desc->bUnitID) +#define USB_IN_FU_ID (in_feature_unit_desc->bUnitID) #define CONTROL_ABSENT 0 #define CONTROL_RDONLY 1 @@ -34,6 +39,8 @@ #define CLK_FREQ_CTRL 0 #define CLK_VLD_CTRL 2 +#define FU_MUTE_CTRL 0 +#define FU_VOL_CTRL 2 #define COPY_CTRL 0 #define CONN_CTRL 2 @@ -44,12 +51,24 @@ #define EPIN_EN(_opts) ((_opts)->p_chmask != 0) #define EPOUT_EN(_opts) ((_opts)->c_chmask != 0) +#define FUIN_EN(_opts) (EPIN_EN(_opts) \ + && ((_opts)->p_mute_present \ + || (_opts)->p_volume_present)) +#define FUOUT_EN(_opts) (EPOUT_EN(_opts) \ + && ((_opts)->c_mute_present \ + || (_opts)->c_volume_present)) #define EPOUT_FBACK_IN_EN(_opts) ((_opts)->c_sync == USB_ENDPOINT_SYNC_ASYNC) struct f_uac2 { struct g_audio g_audio; u8 ac_intf, as_in_intf, as_out_intf; u8 ac_alt, as_in_alt, as_out_alt; /* needed for get_alt() */ + + struct usb_ctrlrequest setup_cr; /* will be used in data stage */ + + /* Interrupt IN endpoint of AC interface */ + struct usb_ep *int_ep; + atomic_t int_count; }; static inline struct f_uac2 *func_to_uac2(struct usb_function *f) @@ -63,6 +82,8 @@ struct f_uac2_opts *g_audio_to_uac2_opts(struct g_audio *agdev) return container_of(agdev->func.fi, struct f_uac2_opts, func_inst); } +static int afunc_notify(struct g_audio *agdev, int unit_id, int cs); + /* --------- USB Function Interface ------------- */ enum { @@ -74,6 +95,8 @@ enum { STR_IO_IT, STR_USB_OT, STR_IO_OT, + STR_FU_IN, + STR_FU_OUT, STR_AS_OUT_ALT0, STR_AS_OUT_ALT1, STR_AS_IN_ALT0, @@ -92,6 +115,8 @@ static struct usb_string strings_fn[] = { [STR_IO_IT].s = "USBD Out", [STR_USB_OT].s = "USBH In", [STR_IO_OT].s = "USBD In", + [STR_FU_IN].s = "Capture Volume", + [STR_FU_OUT].s = "Playback Volume", [STR_AS_OUT_ALT0].s = "Playback Inactive", [STR_AS_OUT_ALT1].s = "Playback Active", [STR_AS_IN_ALT0].s = "Capture Inactive", @@ -126,7 +151,7 @@ static struct usb_interface_descriptor std_ac_if_desc = { .bDescriptorType = USB_DT_INTERFACE, .bAlternateSetting = 0, - .bNumEndpoints = 0, + /* .bNumEndpoints = DYNAMIC */ .bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, .bInterfaceProtocol = UAC_VERSION_2, @@ -212,6 +237,9 @@ static struct uac2_output_terminal_descriptor io_out_ot_desc = { .bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL), }; +static struct uac2_feature_unit_descriptor *in_feature_unit_desc; +static struct uac2_feature_unit_descriptor *out_feature_unit_desc; + static struct uac2_ac_header_descriptor ac_hdr_desc = { .bLength = sizeof ac_hdr_desc, .bDescriptorType = USB_DT_CS_INTERFACE, @@ -223,6 +251,36 @@ static struct uac2_ac_header_descriptor ac_hdr_desc = { .bmControls = 0, }; +/* AC IN Interrupt Endpoint */ +static struct usb_endpoint_descriptor fs_ep_int_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(6), + .bInterval = 1, +}; + +static struct usb_endpoint_descriptor hs_ep_int_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(6), + .bInterval = 4, +}; + +static struct usb_endpoint_descriptor ss_ep_int_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(6), + .bInterval = 4, +}; + /* Audio Streaming OUT Interface - Alt0 */ static struct usb_interface_descriptor std_as_out_if0_desc = { .bLength = sizeof std_as_out_if0_desc, @@ -452,10 +510,14 @@ static struct usb_descriptor_header *fs_audio_desc[] = { (struct usb_descriptor_header *)&in_clk_src_desc, (struct usb_descriptor_header *)&out_clk_src_desc, (struct usb_descriptor_header *)&usb_out_it_desc, + (struct usb_descriptor_header *)&out_feature_unit_desc, (struct usb_descriptor_header *)&io_in_it_desc, (struct usb_descriptor_header *)&usb_in_ot_desc, + (struct usb_descriptor_header *)&in_feature_unit_desc, (struct usb_descriptor_header *)&io_out_ot_desc, + (struct usb_descriptor_header *)&fs_ep_int_desc, + (struct usb_descriptor_header *)&std_as_out_if0_desc, (struct usb_descriptor_header *)&std_as_out_if1_desc, @@ -483,10 +545,14 @@ static struct usb_descriptor_header *hs_audio_desc[] = { (struct usb_descriptor_header *)&in_clk_src_desc, (struct usb_descriptor_header *)&out_clk_src_desc, (struct usb_descriptor_header *)&usb_out_it_desc, + (struct usb_descriptor_header *)&out_feature_unit_desc, (struct usb_descriptor_header *)&io_in_it_desc, (struct usb_descriptor_header *)&usb_in_ot_desc, + (struct usb_descriptor_header *)&in_feature_unit_desc, (struct usb_descriptor_header *)&io_out_ot_desc, + (struct usb_descriptor_header *)&hs_ep_int_desc, + (struct usb_descriptor_header *)&std_as_out_if0_desc, (struct usb_descriptor_header *)&std_as_out_if1_desc, @@ -514,10 +580,14 @@ static struct usb_descriptor_header *ss_audio_desc[] = { (struct usb_descriptor_header *)&in_clk_src_desc, (struct usb_descriptor_header *)&out_clk_src_desc, (struct usb_descriptor_header *)&usb_out_it_desc, + (struct usb_descriptor_header *)&out_feature_unit_desc, (struct usb_descriptor_header *)&io_in_it_desc, (struct usb_descriptor_header *)&usb_in_ot_desc, + (struct usb_descriptor_header *)&in_feature_unit_desc, (struct usb_descriptor_header *)&io_out_ot_desc, + (struct usb_descriptor_header *)&ss_ep_int_desc, + (struct usb_descriptor_header *)&std_as_out_if0_desc, (struct usb_descriptor_header *)&std_as_out_if1_desc, @@ -539,6 +609,17 @@ static struct usb_descriptor_header *ss_audio_desc[] = { NULL, }; +struct cntrl_cur_lay2 { + __le16 wCUR; +}; + +struct cntrl_range_lay2 { + __le16 wNumSubRanges; + __le16 wMIN; + __le16 wMAX; + __le16 wRES; +} __packed; + struct cntrl_cur_lay3 { __le32 dCUR; }; @@ -595,6 +676,26 @@ static int set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, return 0; } +static struct uac2_feature_unit_descriptor *build_fu_desc(int chmask) +{ + struct uac2_feature_unit_descriptor *fu_desc; + int channels = num_channels(chmask); + int fu_desc_size = UAC2_DT_FEATURE_UNIT_SIZE(channels); + + fu_desc = kzalloc(fu_desc_size, GFP_KERNEL); + if (!fu_desc) + return NULL; + + fu_desc->bLength = fu_desc_size; + fu_desc->bDescriptorType = USB_DT_CS_INTERFACE; + + fu_desc->bDescriptorSubtype = UAC_FEATURE_UNIT; + + /* bUnitID, bSourceID and bmaControls will be defined later */ + + return fu_desc; +} + /* Use macro to overcome line length limitation */ #define USBDHDR(p) (struct usb_descriptor_header *)(p) @@ -607,6 +708,7 @@ static void setup_headers(struct f_uac2_opts *opts, struct usb_endpoint_descriptor *epout_desc; struct usb_endpoint_descriptor *epin_desc; struct usb_endpoint_descriptor *epin_fback_desc; + struct usb_endpoint_descriptor *ep_int_desc; int i; switch (speed) { @@ -614,11 +716,13 @@ static void setup_headers(struct f_uac2_opts *opts, epout_desc = &fs_epout_desc; epin_desc = &fs_epin_desc; epin_fback_desc = &fs_epin_fback_desc; + ep_int_desc = &fs_ep_int_desc; break; case USB_SPEED_HIGH: epout_desc = &hs_epout_desc; epin_desc = &hs_epin_desc; epin_fback_desc = &hs_epin_fback_desc; + ep_int_desc = &hs_ep_int_desc; break; default: epout_desc = &ss_epout_desc; @@ -626,6 +730,7 @@ static void setup_headers(struct f_uac2_opts *opts, epout_desc_comp = &ss_epout_desc_comp; epin_desc_comp = &ss_epin_desc_comp; epin_fback_desc = &ss_epin_fback_desc; + ep_int_desc = &ss_ep_int_desc; } i = 0; @@ -637,13 +742,27 @@ static void setup_headers(struct f_uac2_opts *opts, if (EPOUT_EN(opts)) { headers[i++] = USBDHDR(&out_clk_src_desc); headers[i++] = USBDHDR(&usb_out_it_desc); - } + + if (FUOUT_EN(opts)) + headers[i++] = USBDHDR(out_feature_unit_desc); + } + if (EPIN_EN(opts)) { headers[i++] = USBDHDR(&io_in_it_desc); + + if (FUIN_EN(opts)) + headers[i++] = USBDHDR(in_feature_unit_desc); + headers[i++] = USBDHDR(&usb_in_ot_desc); } - if (EPOUT_EN(opts)) { + + if (EPOUT_EN(opts)) headers[i++] = USBDHDR(&io_out_ot_desc); + + if (FUOUT_EN(opts) || FUIN_EN(opts)) + headers[i++] = USBDHDR(ep_int_desc); + + if (EPOUT_EN(opts)) { headers[i++] = USBDHDR(&std_as_out_if0_desc); headers[i++] = USBDHDR(&std_as_out_if1_desc); headers[i++] = USBDHDR(&as_out_hdr_desc); @@ -657,6 +776,7 @@ static void setup_headers(struct f_uac2_opts *opts, if (EPOUT_FBACK_IN_EN(opts)) headers[i++] = USBDHDR(epin_fback_desc); } + if (EPIN_EN(opts)) { headers[i++] = USBDHDR(&std_as_in_if0_desc); headers[i++] = USBDHDR(&std_as_in_if1_desc); @@ -684,17 +804,35 @@ static void setup_descriptor(struct f_uac2_opts *opts) io_out_ot_desc.bTerminalID = i++; if (EPIN_EN(opts)) usb_in_ot_desc.bTerminalID = i++; + if (FUOUT_EN(opts)) + out_feature_unit_desc->bUnitID = i++; + if (FUIN_EN(opts)) + in_feature_unit_desc->bUnitID = i++; if (EPOUT_EN(opts)) out_clk_src_desc.bClockID = i++; if (EPIN_EN(opts)) in_clk_src_desc.bClockID = i++; usb_out_it_desc.bCSourceID = out_clk_src_desc.bClockID; - usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID; + + if (FUIN_EN(opts)) { + usb_in_ot_desc.bSourceID = in_feature_unit_desc->bUnitID; + in_feature_unit_desc->bSourceID = io_in_it_desc.bTerminalID; + } else { + usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID; + } + usb_in_ot_desc.bCSourceID = in_clk_src_desc.bClockID; io_in_it_desc.bCSourceID = in_clk_src_desc.bClockID; io_out_ot_desc.bCSourceID = out_clk_src_desc.bClockID; - io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID; + + if (FUOUT_EN(opts)) { + io_out_ot_desc.bSourceID = out_feature_unit_desc->bUnitID; + out_feature_unit_desc->bSourceID = usb_out_it_desc.bTerminalID; + } else { + io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID; + } + as_out_hdr_desc.bTerminalLink = usb_out_it_desc.bTerminalID; as_in_hdr_desc.bTerminalLink = usb_in_ot_desc.bTerminalID; @@ -706,6 +844,10 @@ static void setup_descriptor(struct f_uac2_opts *opts) len += sizeof(in_clk_src_desc); len += sizeof(usb_in_ot_desc); + + if (FUIN_EN(opts)) + len += in_feature_unit_desc->bLength; + len += sizeof(io_in_it_desc); ac_hdr_desc.wTotalLength = cpu_to_le16(len); iad_desc.bInterfaceCount++; @@ -715,6 +857,10 @@ static void setup_descriptor(struct f_uac2_opts *opts) len += sizeof(out_clk_src_desc); len += sizeof(usb_out_it_desc); + + if (FUOUT_EN(opts)) + len += out_feature_unit_desc->bLength; + len += sizeof(io_out_ot_desc); ac_hdr_desc.wTotalLength = cpu_to_le16(len); iad_desc.bInterfaceCount++; @@ -752,6 +898,28 @@ static int afunc_validate_opts(struct g_audio *agdev, struct device *dev) return -EINVAL; } + if (opts->p_volume_max <= opts->p_volume_min) { + dev_err(dev, "Error: incorrect playback volume max/min\n"); + return -EINVAL; + } else if (opts->c_volume_max <= opts->c_volume_min) { + dev_err(dev, "Error: incorrect capture volume max/min\n"); + return -EINVAL; + } else if (opts->p_volume_res <= 0) { + dev_err(dev, "Error: negative/zero playback volume resolution\n"); + return -EINVAL; + } else if (opts->c_volume_res <= 0) { + dev_err(dev, "Error: negative/zero capture volume resolution\n"); + return -EINVAL; + } + + if ((opts->p_volume_max - opts->p_volume_min) % opts->p_volume_res) { + dev_err(dev, "Error: incorrect playback volume resolution\n"); + return -EINVAL; + } else if ((opts->c_volume_max - opts->c_volume_min) % opts->c_volume_res) { + dev_err(dev, "Error: incorrect capture volume resolution\n"); + return -EINVAL; + } + return 0; } @@ -774,6 +942,20 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) us = usb_gstrings_attach(cdev, fn_strings, ARRAY_SIZE(strings_fn)); if (IS_ERR(us)) return PTR_ERR(us); + + if (FUOUT_EN(uac2_opts)) { + out_feature_unit_desc = build_fu_desc(uac2_opts->c_chmask); + if (!out_feature_unit_desc) + return -ENOMEM; + } + if (FUIN_EN(uac2_opts)) { + in_feature_unit_desc = build_fu_desc(uac2_opts->p_chmask); + if (!in_feature_unit_desc) { + ret = -ENOMEM; + goto err_free_fu; + } + } + iad_desc.iFunction = us[STR_ASSOC].id; std_ac_if_desc.iInterface = us[STR_IF_CTRL].id; in_clk_src_desc.iClockSource = us[STR_CLKSRC_IN].id; @@ -787,6 +969,21 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) std_as_in_if0_desc.iInterface = us[STR_AS_IN_ALT0].id; std_as_in_if1_desc.iInterface = us[STR_AS_IN_ALT1].id; + if (FUOUT_EN(uac2_opts)) { + u8 *i_feature = (u8 *)out_feature_unit_desc; + + i_feature = (u8 *)out_feature_unit_desc + + out_feature_unit_desc->bLength - 1; + *i_feature = us[STR_FU_OUT].id; + } + if (FUIN_EN(uac2_opts)) { + u8 *i_feature = (u8 *)in_feature_unit_desc; + + i_feature = (u8 *)in_feature_unit_desc + + in_feature_unit_desc->bLength - 1; + *i_feature = us[STR_FU_IN].id; + } + /* Initialize the configurable parameters */ usb_out_it_desc.bNrChannels = num_channels(uac2_opts->c_chmask); @@ -801,6 +998,26 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) as_out_fmt1_desc.bBitResolution = uac2_opts->c_ssize * 8; as_in_fmt1_desc.bSubslotSize = uac2_opts->p_ssize; as_in_fmt1_desc.bBitResolution = uac2_opts->p_ssize * 8; + if (FUOUT_EN(uac2_opts)) { + __le32 *bma = (__le32 *)&out_feature_unit_desc->bmaControls[0]; + u32 control = 0; + + if (uac2_opts->c_mute_present) + control |= CONTROL_RDWR << FU_MUTE_CTRL; + if (uac2_opts->c_volume_present) + control |= CONTROL_RDWR << FU_VOL_CTRL; + *bma = cpu_to_le32(control); + } + if (FUIN_EN(uac2_opts)) { + __le32 *bma = (__le32 *)&in_feature_unit_desc->bmaControls[0]; + u32 control = 0; + + if (uac2_opts->p_mute_present) + control |= CONTROL_RDWR << FU_MUTE_CTRL; + if (uac2_opts->p_volume_present) + control |= CONTROL_RDWR << FU_VOL_CTRL; + *bma = cpu_to_le32(control); + } snprintf(clksrc_in, sizeof(clksrc_in), "%uHz", uac2_opts->p_srate); snprintf(clksrc_out, sizeof(clksrc_out), "%uHz", uac2_opts->c_srate); @@ -808,7 +1025,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) ret = usb_interface_id(cfg, fn); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - return ret; + goto err_free_fu; } iad_desc.bFirstInterface = ret; @@ -820,7 +1037,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) ret = usb_interface_id(cfg, fn); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - return ret; + goto err_free_fu; } std_as_out_if0_desc.bInterfaceNumber = ret; std_as_out_if1_desc.bInterfaceNumber = ret; @@ -849,7 +1066,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) ret = usb_interface_id(cfg, fn); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - return ret; + goto err_free_fu; } std_as_in_if0_desc.bInterfaceNumber = ret; std_as_in_if1_desc.bInterfaceNumber = ret; @@ -857,6 +1074,17 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) uac2->as_in_alt = 0; } + if (FUOUT_EN(uac2_opts) || FUIN_EN(uac2_opts)) { + uac2->int_ep = usb_ep_autoconfig(gadget, &fs_ep_int_desc); + if (!uac2->int_ep) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + ret = -ENODEV; + goto err_free_fu; + } + + std_ac_if_desc.bNumEndpoints = 1; + } + /* Calculate wMaxPacketSize according to audio bandwidth */ ret = set_ep_max_packet_size(uac2_opts, &fs_epin_desc, USB_SPEED_FULL, true); @@ -904,7 +1132,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc); if (!agdev->out_ep) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - return -ENODEV; + ret = -ENODEV; + goto err_free_fu; } if (EPOUT_FBACK_IN_EN(uac2_opts)) { agdev->in_ep_fback = usb_ep_autoconfig(gadget, @@ -912,7 +1141,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) if (!agdev->in_ep_fback) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - return -ENODEV; + ret = -ENODEV; + goto err_free_fu; } } } @@ -921,7 +1151,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc); if (!agdev->in_ep) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - return -ENODEV; + ret = -ENODEV; + goto err_free_fu; } } @@ -937,38 +1168,137 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) agdev->out_ep_maxpsize = max_t(u16, agdev->out_ep_maxpsize, le16_to_cpu(ss_epout_desc.wMaxPacketSize)); + // HS and SS endpoint addresses are copied from autoconfigured FS descriptors + hs_ep_int_desc.bEndpointAddress = fs_ep_int_desc.bEndpointAddress; hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress; hs_epin_fback_desc.bEndpointAddress = fs_epin_fback_desc.bEndpointAddress; hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress; ss_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress; ss_epin_fback_desc.bEndpointAddress = fs_epin_fback_desc.bEndpointAddress; ss_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress; + ss_ep_int_desc.bEndpointAddress = fs_ep_int_desc.bEndpointAddress; setup_descriptor(uac2_opts); ret = usb_assign_descriptors(fn, fs_audio_desc, hs_audio_desc, ss_audio_desc, ss_audio_desc); if (ret) - return ret; + goto err_free_fu; agdev->gadget = gadget; agdev->params.p_chmask = uac2_opts->p_chmask; agdev->params.p_srate = uac2_opts->p_srate; agdev->params.p_ssize = uac2_opts->p_ssize; + if (FUIN_EN(uac2_opts)) { + agdev->params.p_fu.id = USB_IN_FU_ID; + agdev->params.p_fu.mute_present = uac2_opts->p_mute_present; + agdev->params.p_fu.volume_present = uac2_opts->p_volume_present; + agdev->params.p_fu.volume_min = uac2_opts->p_volume_min; + agdev->params.p_fu.volume_max = uac2_opts->p_volume_max; + agdev->params.p_fu.volume_res = uac2_opts->p_volume_res; + } agdev->params.c_chmask = uac2_opts->c_chmask; agdev->params.c_srate = uac2_opts->c_srate; agdev->params.c_ssize = uac2_opts->c_ssize; + if (FUOUT_EN(uac2_opts)) { + agdev->params.c_fu.id = USB_OUT_FU_ID; + agdev->params.c_fu.mute_present = uac2_opts->c_mute_present; + agdev->params.c_fu.volume_present = uac2_opts->c_volume_present; + agdev->params.c_fu.volume_min = uac2_opts->c_volume_min; + agdev->params.c_fu.volume_max = uac2_opts->c_volume_max; + agdev->params.c_fu.volume_res = uac2_opts->c_volume_res; + } agdev->params.req_number = uac2_opts->req_number; agdev->params.fb_max = uac2_opts->fb_max; + + if (FUOUT_EN(uac2_opts) || FUIN_EN(uac2_opts)) + agdev->notify = afunc_notify; + ret = g_audio_setup(agdev, "UAC2 PCM", "UAC2_Gadget"); if (ret) goto err_free_descs; + return 0; err_free_descs: usb_free_all_descriptors(fn); agdev->gadget = NULL; +err_free_fu: + kfree(out_feature_unit_desc); + out_feature_unit_desc = NULL; + kfree(in_feature_unit_desc); + in_feature_unit_desc = NULL; + return ret; +} + +static void +afunc_notify_complete(struct usb_ep *_ep, struct usb_request *req) +{ + struct g_audio *agdev = req->context; + struct f_uac2 *uac2 = func_to_uac2(&agdev->func); + + atomic_dec(&uac2->int_count); + kfree(req->buf); + usb_ep_free_request(_ep, req); +} + +static int +afunc_notify(struct g_audio *agdev, int unit_id, int cs) +{ + struct f_uac2 *uac2 = func_to_uac2(&agdev->func); + struct usb_request *req; + struct uac2_interrupt_data_msg *msg; + u16 w_index, w_value; + int ret; + + if (!uac2->int_ep->enabled) + return 0; + + if (atomic_inc_return(&uac2->int_count) > UAC2_DEF_INT_REQ_NUM) { + atomic_dec(&uac2->int_count); + return 0; + } + + req = usb_ep_alloc_request(uac2->int_ep, GFP_ATOMIC); + if (req == NULL) { + ret = -ENOMEM; + goto err_dec_int_count; + } + + msg = kzalloc(sizeof(*msg), GFP_ATOMIC); + if (msg == NULL) { + ret = -ENOMEM; + goto err_free_request; + } + + w_index = unit_id << 8 | uac2->ac_intf; + w_value = cs << 8; + + msg->bInfo = 0; /* Non-vendor, interface interrupt */ + msg->bAttribute = UAC2_CS_CUR; + msg->wIndex = cpu_to_le16(w_index); + msg->wValue = cpu_to_le16(w_value); + + req->length = sizeof(*msg); + req->buf = msg; + req->context = agdev; + req->complete = afunc_notify_complete; + + ret = usb_ep_queue(uac2->int_ep, req, GFP_ATOMIC); + + if (ret) + goto err_free_msg; + + return 0; + +err_free_msg: + kfree(msg); +err_free_request: + usb_ep_free_request(uac2->int_ep, req); +err_dec_int_count: + atomic_dec(&uac2->int_count); + return ret; } @@ -977,6 +1307,7 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) { struct usb_composite_dev *cdev = fn->config->cdev; struct f_uac2 *uac2 = func_to_uac2(fn); + struct g_audio *agdev = func_to_g_audio(fn); struct usb_gadget *gadget = cdev->gadget; struct device *dev = &gadget->dev; int ret = 0; @@ -993,6 +1324,14 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return -EINVAL; } + + /* restart interrupt endpoint */ + if (uac2->int_ep) { + usb_ep_disable(uac2->int_ep); + config_ep_by_speed(gadget, &agdev->func, uac2->int_ep); + usb_ep_enable(uac2->int_ep); + } + return 0; } @@ -1047,6 +1386,8 @@ afunc_disable(struct usb_function *fn) uac2->as_out_alt = 0; u_audio_stop_capture(&uac2->g_audio); u_audio_stop_playback(&uac2->g_audio); + if (uac2->int_ep) + usb_ep_disable(uac2->int_ep); } static int @@ -1054,7 +1395,7 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) { struct usb_request *req = fn->config->cdev->req; struct g_audio *agdev = func_to_g_audio(fn); - struct f_uac2_opts *opts; + struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev); u16 w_length = le16_to_cpu(cr->wLength); u16 w_index = le16_to_cpu(cr->wIndex); u16 w_value = le16_to_cpu(cr->wValue); @@ -1063,28 +1404,64 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) int value = -EOPNOTSUPP; int p_srate, c_srate; - opts = g_audio_to_uac2_opts(agdev); p_srate = opts->p_srate; c_srate = opts->c_srate; - if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { - struct cntrl_cur_lay3 c; - memset(&c, 0, sizeof(struct cntrl_cur_lay3)); + if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) { + if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { + struct cntrl_cur_lay3 c; + + memset(&c, 0, sizeof(struct cntrl_cur_lay3)); + + if (entity_id == USB_IN_CLK_ID) + c.dCUR = cpu_to_le32(p_srate); + else if (entity_id == USB_OUT_CLK_ID) + c.dCUR = cpu_to_le32(c_srate); + + value = min_t(unsigned int, w_length, sizeof(c)); + memcpy(req->buf, &c, value); + } else if (control_selector == UAC2_CS_CONTROL_CLOCK_VALID) { + *(u8 *)req->buf = 1; + value = min_t(unsigned int, w_length, 1); + } else { + dev_err(&agdev->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; - if (entity_id == USB_IN_CLK_ID) - c.dCUR = cpu_to_le32(p_srate); - else if (entity_id == USB_OUT_CLK_ID) - c.dCUR = cpu_to_le32(c_srate); + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; - value = min_t(unsigned, w_length, sizeof c); - memcpy(req->buf, &c, value); - } else if (control_selector == UAC2_CS_CONTROL_CLOCK_VALID) { - *(u8 *)req->buf = 1; - value = min_t(unsigned, w_length, 1); + if (control_selector == UAC_FU_MUTE) { + unsigned int mute; + + u_audio_get_mute(agdev, is_playback, &mute); + + *(u8 *)req->buf = mute; + value = min_t(unsigned int, w_length, 1); + } else if (control_selector == UAC_FU_VOLUME) { + struct cntrl_cur_lay2 c; + s16 volume; + + memset(&c, 0, sizeof(struct cntrl_cur_lay2)); + + u_audio_get_volume(agdev, is_playback, &volume); + c.wCUR = cpu_to_le16(volume); + + value = min_t(unsigned int, w_length, sizeof(c)); + memcpy(req->buf, &c, value); + } else { + dev_err(&agdev->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } } else { dev_err(&agdev->gadget->dev, - "%s:%d control_selector=%d TODO!\n", - __func__, __LINE__, control_selector); + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); } return value; @@ -1095,38 +1472,77 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr) { struct usb_request *req = fn->config->cdev->req; struct g_audio *agdev = func_to_g_audio(fn); - struct f_uac2_opts *opts; + struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev); u16 w_length = le16_to_cpu(cr->wLength); u16 w_index = le16_to_cpu(cr->wIndex); u16 w_value = le16_to_cpu(cr->wValue); u8 entity_id = (w_index >> 8) & 0xff; u8 control_selector = w_value >> 8; - struct cntrl_range_lay3 r; int value = -EOPNOTSUPP; int p_srate, c_srate; - opts = g_audio_to_uac2_opts(agdev); p_srate = opts->p_srate; c_srate = opts->c_srate; - if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { - if (entity_id == USB_IN_CLK_ID) - r.dMIN = cpu_to_le32(p_srate); - else if (entity_id == USB_OUT_CLK_ID) - r.dMIN = cpu_to_le32(c_srate); - else - return -EOPNOTSUPP; + if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) { + if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { + struct cntrl_range_lay3 r; + + if (entity_id == USB_IN_CLK_ID) + r.dMIN = cpu_to_le32(p_srate); + else if (entity_id == USB_OUT_CLK_ID) + r.dMIN = cpu_to_le32(c_srate); + else + return -EOPNOTSUPP; - r.dMAX = r.dMIN; - r.dRES = 0; - r.wNumSubRanges = cpu_to_le16(1); + r.dMAX = r.dMIN; + r.dRES = 0; + r.wNumSubRanges = cpu_to_le16(1); - value = min_t(unsigned, w_length, sizeof r); - memcpy(req->buf, &r, value); + value = min_t(unsigned int, w_length, sizeof(r)); + memcpy(req->buf, &r, value); + } else { + dev_err(&agdev->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_VOLUME) { + struct cntrl_range_lay2 r; + s16 max_db, min_db, res_db; + + if (is_playback) { + max_db = opts->p_volume_max; + min_db = opts->p_volume_min; + res_db = opts->p_volume_res; + } else { + max_db = opts->c_volume_max; + min_db = opts->c_volume_min; + res_db = opts->c_volume_res; + } + + r.wMAX = cpu_to_le16(max_db); + r.wMIN = cpu_to_le16(min_db); + r.wRES = cpu_to_le16(res_db); + r.wNumSubRanges = cpu_to_le16(1); + + value = min_t(unsigned int, w_length, sizeof(r)); + memcpy(req->buf, &r, value); + } else { + dev_err(&agdev->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } } else { dev_err(&agdev->gadget->dev, - "%s:%d control_selector=%d TODO!\n", - __func__, __LINE__, control_selector); + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); } return value; @@ -1143,16 +1559,82 @@ ac_rq_in(struct usb_function *fn, const struct usb_ctrlrequest *cr) return -EOPNOTSUPP; } +static void +out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct g_audio *agdev = req->context; + struct usb_composite_dev *cdev = agdev->func.config->cdev; + struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev); + struct f_uac2 *uac2 = func_to_uac2(&agdev->func); + struct usb_ctrlrequest *cr = &uac2->setup_cr; + u16 w_index = le16_to_cpu(cr->wIndex); + u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; + u8 control_selector = w_value >> 8; + + if (req->status != 0) { + dev_dbg(&cdev->gadget->dev, "completion err %d\n", req->status); + return; + } + + if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_MUTE) { + u8 mute = *(u8 *)req->buf; + + u_audio_set_mute(agdev, is_playback, mute); + + return; + } else if (control_selector == UAC_FU_VOLUME) { + struct cntrl_cur_lay2 *c = req->buf; + s16 volume; + + volume = le16_to_cpu(c->wCUR); + u_audio_set_volume(agdev, is_playback, volume); + + return; + } else { + dev_err(&agdev->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + usb_ep_set_halt(ep); + } + } +} + static int out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) { + struct usb_request *req = fn->config->cdev->req; + struct g_audio *agdev = func_to_g_audio(fn); + struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev); + struct f_uac2 *uac2 = func_to_uac2(fn); u16 w_length = le16_to_cpu(cr->wLength); + u16 w_index = le16_to_cpu(cr->wIndex); u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; u8 control_selector = w_value >> 8; - if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) - return w_length; + if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) { + if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) + return w_length; + } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + memcpy(&uac2->setup_cr, cr, sizeof(*cr)); + req->context = agdev; + req->complete = out_rq_cur_complete; + return w_length; + } else { + dev_err(&agdev->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } return -EOPNOTSUPP; } @@ -1228,7 +1710,15 @@ static struct configfs_item_operations f_uac2_item_ops = { .release = f_uac2_attr_release, }; -#define UAC2_ATTRIBUTE(name) \ +#define uac2_kstrtou32 kstrtou32 +#define uac2_kstrtos16 kstrtos16 +#define uac2_kstrtobool(s, base, res) kstrtobool((s), (res)) + +static const char *u32_fmt = "%u\n"; +static const char *s16_fmt = "%hd\n"; +static const char *bool_fmt = "%u\n"; + +#define UAC2_ATTRIBUTE(type, name) \ static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \ char *page) \ { \ @@ -1236,7 +1726,7 @@ static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \ int result; \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%u\n", opts->name); \ + result = sprintf(page, type##_fmt, opts->name); \ mutex_unlock(&opts->lock); \ \ return result; \ @@ -1247,7 +1737,7 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \ { \ struct f_uac2_opts *opts = to_f_uac2_opts(item); \ int ret; \ - u32 num; \ + type num; \ \ mutex_lock(&opts->lock); \ if (opts->refcnt) { \ @@ -1255,7 +1745,7 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \ goto end; \ } \ \ - ret = kstrtou32(page, 0, &num); \ + ret = uac2_kstrto##type(page, 0, &num); \ if (ret) \ goto end; \ \ @@ -1325,15 +1815,27 @@ end: \ \ CONFIGFS_ATTR(f_uac2_opts_, name) -UAC2_ATTRIBUTE(p_chmask); -UAC2_ATTRIBUTE(p_srate); -UAC2_ATTRIBUTE(p_ssize); -UAC2_ATTRIBUTE(c_chmask); -UAC2_ATTRIBUTE(c_srate); +UAC2_ATTRIBUTE(u32, p_chmask); +UAC2_ATTRIBUTE(u32, p_srate); +UAC2_ATTRIBUTE(u32, p_ssize); +UAC2_ATTRIBUTE(u32, c_chmask); +UAC2_ATTRIBUTE(u32, c_srate); UAC2_ATTRIBUTE_SYNC(c_sync); -UAC2_ATTRIBUTE(c_ssize); -UAC2_ATTRIBUTE(req_number); -UAC2_ATTRIBUTE(fb_max); +UAC2_ATTRIBUTE(u32, c_ssize); +UAC2_ATTRIBUTE(u32, req_number); + +UAC2_ATTRIBUTE(bool, p_mute_present); +UAC2_ATTRIBUTE(bool, p_volume_present); +UAC2_ATTRIBUTE(s16, p_volume_min); +UAC2_ATTRIBUTE(s16, p_volume_max); +UAC2_ATTRIBUTE(s16, p_volume_res); + +UAC2_ATTRIBUTE(bool, c_mute_present); +UAC2_ATTRIBUTE(bool, c_volume_present); +UAC2_ATTRIBUTE(s16, c_volume_min); +UAC2_ATTRIBUTE(s16, c_volume_max); +UAC2_ATTRIBUTE(s16, c_volume_res); +UAC2_ATTRIBUTE(u32, fb_max); static struct configfs_attribute *f_uac2_attrs[] = { &f_uac2_opts_attr_p_chmask, @@ -1345,6 +1847,19 @@ static struct configfs_attribute *f_uac2_attrs[] = { &f_uac2_opts_attr_c_sync, &f_uac2_opts_attr_req_number, &f_uac2_opts_attr_fb_max, + + &f_uac2_opts_attr_p_mute_present, + &f_uac2_opts_attr_p_volume_present, + &f_uac2_opts_attr_p_volume_min, + &f_uac2_opts_attr_p_volume_max, + &f_uac2_opts_attr_p_volume_res, + + &f_uac2_opts_attr_c_mute_present, + &f_uac2_opts_attr_c_volume_present, + &f_uac2_opts_attr_c_volume_min, + &f_uac2_opts_attr_c_volume_max, + &f_uac2_opts_attr_c_volume_res, + NULL, }; @@ -1383,6 +1898,19 @@ static struct usb_function_instance *afunc_alloc_inst(void) opts->c_srate = UAC2_DEF_CSRATE; opts->c_ssize = UAC2_DEF_CSSIZE; opts->c_sync = UAC2_DEF_CSYNC; + + opts->p_mute_present = UAC2_DEF_MUTE_PRESENT; + opts->p_volume_present = UAC2_DEF_VOLUME_PRESENT; + opts->p_volume_min = UAC2_DEF_MIN_DB; + opts->p_volume_max = UAC2_DEF_MAX_DB; + opts->p_volume_res = UAC2_DEF_RES_DB; + + opts->c_mute_present = UAC2_DEF_MUTE_PRESENT; + opts->c_volume_present = UAC2_DEF_VOLUME_PRESENT; + opts->c_volume_min = UAC2_DEF_MIN_DB; + opts->c_volume_max = UAC2_DEF_MAX_DB; + opts->c_volume_res = UAC2_DEF_RES_DB; + opts->req_number = UAC2_DEF_REQ_NUM; opts->fb_max = UAC2_DEF_FB_MAX; return &opts->func_inst; @@ -1409,6 +1937,11 @@ static void afunc_unbind(struct usb_configuration *c, struct usb_function *f) usb_free_all_descriptors(f); agdev->gadget = NULL; + + kfree(out_feature_unit_desc); + out_feature_unit_desc = NULL; + kfree(in_feature_unit_desc); + in_feature_unit_desc = NULL; } static struct usb_function *afunc_alloc(struct usb_function_instance *fi) @@ -1441,3 +1974,4 @@ DECLARE_USB_FUNCTION_INIT(uac2, afunc_alloc_inst, afunc_alloc); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Yadwinder Singh"); MODULE_AUTHOR("Jaswinder Singh"); +MODULE_AUTHOR("Ruslan Bilovol"); diff --git a/drivers/usb/gadget/function/u_uac2.h b/drivers/usb/gadget/function/u_uac2.h index 179d3ef6a195..a73b35774c44 100644 --- a/drivers/usb/gadget/function/u_uac2.h +++ b/drivers/usb/gadget/function/u_uac2.h @@ -22,8 +22,16 @@ #define UAC2_DEF_CSRATE 64000 #define UAC2_DEF_CSSIZE 2 #define UAC2_DEF_CSYNC USB_ENDPOINT_SYNC_ASYNC + +#define UAC2_DEF_MUTE_PRESENT 1 +#define UAC2_DEF_VOLUME_PRESENT 1 +#define UAC2_DEF_MIN_DB (-100*256) /* -100 dB */ +#define UAC2_DEF_MAX_DB 0 /* 0 dB */ +#define UAC2_DEF_RES_DB (1*256) /* 1 dB */ + #define UAC2_DEF_REQ_NUM 2 #define UAC2_DEF_FB_MAX 5 +#define UAC2_DEF_INT_REQ_NUM 10 struct f_uac2_opts { struct usb_function_instance func_inst; @@ -34,9 +42,22 @@ struct f_uac2_opts { int c_srate; int c_ssize; int c_sync; + + bool p_mute_present; + bool p_volume_present; + s16 p_volume_min; + s16 p_volume_max; + s16 p_volume_res; + + bool c_mute_present; + bool c_volume_present; + s16 c_volume_min; + s16 c_volume_max; + s16 c_volume_res; + int req_number; int fb_max; - bool bound; + bool bound; struct mutex lock; int refcnt; -- cgit v1.2.3 From 0356e6283c7177391d144612f4b12986ed5c4f6e Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Mon, 12 Jul 2021 14:55:29 +0200 Subject: usb: gadget: f_uac1: add volume and mute support This adds bi-directional (host->device, device->host) volume/mute support to the f_uac1 driver by adding Feature Units and interrupt endpoint. Currently only master channel is supported. Volume and mute are configurable through configfs, by default volume has -100..0 dB range with 1 dB step. Similar to existing flexible endpoints configuration, Feature Unit won't be added to the topology if both mute and volume are not enabled, also interrupt endpoint isn't added to the device if no feature unit is present Signed-off-by: Ruslan Bilovol Signed-off-by: Pavel Hofman Link: https://lore.kernel.org/r/20210712125529.76070-5-pavel.hofman@ivitera.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/configfs-usb-gadget-uac1 | 10 + Documentation/usb/gadget-testing.rst | 26 +- drivers/usb/gadget/function/f_uac1.c | 674 ++++++++++++++++++++- drivers/usb/gadget/function/u_uac1.h | 20 + 4 files changed, 700 insertions(+), 30 deletions(-) (limited to 'drivers') diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac1 b/Documentation/ABI/testing/configfs-usb-gadget-uac1 index dc23fd776943..dd647d44d975 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uac1 +++ b/Documentation/ABI/testing/configfs-usb-gadget-uac1 @@ -8,9 +8,19 @@ Description: c_chmask capture channel mask c_srate capture sampling rate c_ssize capture sample size (bytes) + c_mute_present capture mute control enable + c_volume_present capture volume control enable + c_volume_min capture volume control min value (in 1/256 dB) + c_volume_max capture volume control max value (in 1/256 dB) + c_volume_res capture volume control resolution (in 1/256 dB) p_chmask playback channel mask p_srate playback sampling rate p_ssize playback sample size (bytes) + p_mute_present playback mute control enable + p_volume_present playback volume control enable + p_volume_min playback volume control min value (in 1/256 dB) + p_volume_max playback volume control max value (in 1/256 dB) + p_volume_res playback volume control resolution (in 1/256 dB) req_number the number of pre-allocated request for both capture and playback ========== =================================== diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index 272e51f17f54..d6253f1a32a1 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -915,14 +915,24 @@ The function name to use when creating the function directory is "uac1". The uac1 function provides these attributes in its function directory: ========== ==================================================== - c_chmask capture channel mask - c_srate capture sampling rate - c_ssize capture sample size (bytes) - p_chmask playback channel mask - p_srate playback sampling rate - p_ssize playback sample size (bytes) - req_number the number of pre-allocated request for both capture - and playback + c_chmask capture channel mask + c_srate capture sampling rate + c_ssize capture sample size (bytes) + c_mute_present capture mute control enable + c_volume_present capture volume control enable + c_volume_min capture volume control min value (in 1/256 dB) + c_volume_max capture volume control max value (in 1/256 dB) + c_volume_res capture volume control resolution (in 1/256 dB) + p_chmask playback channel mask + p_srate playback sampling rate + p_ssize playback sample size (bytes) + p_mute_present playback mute control enable + p_volume_present playback volume control enable + p_volume_min playback volume control min value (in 1/256 dB) + p_volume_max playback volume control max value (in 1/256 dB) + p_volume_res playback volume control resolution (in 1/256 dB) + req_number the number of pre-allocated request for both capture + and playback ========== ==================================================== The attributes have sane default values. diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c index d04707580068..3b3db1a8df75 100644 --- a/drivers/usb/gadget/function/f_uac1.c +++ b/drivers/usb/gadget/function/f_uac1.c @@ -22,13 +22,26 @@ /* UAC1 spec: 3.7.2.3 Audio Channel Cluster Format */ #define UAC1_CHANNEL_MASK 0x0FFF +#define USB_OUT_FU_ID (out_feature_unit_desc->bUnitID) +#define USB_IN_FU_ID (in_feature_unit_desc->bUnitID) + #define EPIN_EN(_opts) ((_opts)->p_chmask != 0) #define EPOUT_EN(_opts) ((_opts)->c_chmask != 0) +#define FUIN_EN(_opts) ((_opts)->p_mute_present \ + || (_opts)->p_volume_present) +#define FUOUT_EN(_opts) ((_opts)->c_mute_present \ + || (_opts)->c_volume_present) struct f_uac1 { struct g_audio g_audio; u8 ac_intf, as_in_intf, as_out_intf; u8 ac_alt, as_in_alt, as_out_alt; /* needed for get_alt() */ + + struct usb_ctrlrequest setup_cr; /* will be used in data stage */ + + /* Interrupt IN endpoint of AC interface */ + struct usb_ep *int_ep; + atomic_t int_count; }; static inline struct f_uac1 *func_to_uac1(struct usb_function *f) @@ -58,7 +71,7 @@ static inline struct f_uac1_opts *g_audio_to_uac1_opts(struct g_audio *audio) static struct usb_interface_descriptor ac_interface_desc = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, - .bNumEndpoints = 0, + /* .bNumEndpoints = DYNAMIC */ .bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, }; @@ -106,6 +119,19 @@ static struct uac1_output_terminal_descriptor usb_in_ot_desc = { /* .bSourceID = DYNAMIC */ }; +static struct uac_feature_unit_descriptor *in_feature_unit_desc; +static struct uac_feature_unit_descriptor *out_feature_unit_desc; + +/* AC IN Interrupt Endpoint */ +static struct usb_endpoint_descriptor ac_int_ep_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(2), + .bInterval = 4, +}; + /* B.4.1 Standard AS Interface Descriptor */ static struct usb_interface_descriptor as_out_interface_alt_0_desc = { .bLength = USB_DT_INTERFACE_SIZE, @@ -232,8 +258,13 @@ static struct usb_descriptor_header *f_audio_desc[] = { (struct usb_descriptor_header *)&usb_out_it_desc, (struct usb_descriptor_header *)&io_out_ot_desc, + (struct usb_descriptor_header *)&out_feature_unit_desc, + (struct usb_descriptor_header *)&io_in_it_desc, (struct usb_descriptor_header *)&usb_in_ot_desc, + (struct usb_descriptor_header *)&in_feature_unit_desc, + + (struct usb_descriptor_header *)&ac_int_ep_desc, (struct usb_descriptor_header *)&as_out_interface_alt_0_desc, (struct usb_descriptor_header *)&as_out_interface_alt_1_desc, @@ -263,6 +294,8 @@ enum { STR_IO_IN_IT, STR_IO_IN_IT_CH_NAMES, STR_USB_IN_OT, + STR_FU_IN, + STR_FU_OUT, STR_AS_OUT_IF_ALT0, STR_AS_OUT_IF_ALT1, STR_AS_IN_IF_ALT0, @@ -277,6 +310,8 @@ static struct usb_string strings_uac1[] = { [STR_IO_IN_IT].s = "Capture Input terminal", [STR_IO_IN_IT_CH_NAMES].s = "Capture Channels", [STR_USB_IN_OT].s = "Capture Output terminal", + [STR_FU_IN].s = "Capture Volume", + [STR_FU_OUT].s = "Playback Volume", [STR_AS_OUT_IF_ALT0].s = "Playback Inactive", [STR_AS_OUT_IF_ALT1].s = "Playback Active", [STR_AS_IN_IF_ALT0].s = "Capture Inactive", @@ -298,6 +333,376 @@ static struct usb_gadget_strings *uac1_strings[] = { * This function is an ALSA sound card following USB Audio Class Spec 1.0. */ +static void audio_notify_complete(struct usb_ep *_ep, struct usb_request *req) +{ + struct g_audio *audio = req->context; + struct f_uac1 *uac1 = func_to_uac1(&audio->func); + + atomic_dec(&uac1->int_count); + kfree(req->buf); + usb_ep_free_request(_ep, req); +} + +static int audio_notify(struct g_audio *audio, int unit_id, int cs) +{ + struct f_uac1 *uac1 = func_to_uac1(&audio->func); + struct usb_request *req; + struct uac1_status_word *msg; + int ret; + + if (!uac1->int_ep->enabled) + return 0; + + if (atomic_inc_return(&uac1->int_count) > UAC1_DEF_INT_REQ_NUM) { + atomic_dec(&uac1->int_count); + return 0; + } + + req = usb_ep_alloc_request(uac1->int_ep, GFP_ATOMIC); + if (req == NULL) { + ret = -ENOMEM; + goto err_dec_int_count; + } + + msg = kmalloc(sizeof(*msg), GFP_ATOMIC); + if (msg == NULL) { + ret = -ENOMEM; + goto err_free_request; + } + + msg->bStatusType = UAC1_STATUS_TYPE_IRQ_PENDING + | UAC1_STATUS_TYPE_ORIG_AUDIO_CONTROL_IF; + msg->bOriginator = unit_id; + + req->length = sizeof(*msg); + req->buf = msg; + req->context = audio; + req->complete = audio_notify_complete; + + ret = usb_ep_queue(uac1->int_ep, req, GFP_ATOMIC); + + if (ret) + goto err_free_msg; + + return 0; + +err_free_msg: + kfree(msg); +err_free_request: + usb_ep_free_request(uac1->int_ep, req); +err_dec_int_count: + atomic_dec(&uac1->int_count); + + return ret; +} + +static int +in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct g_audio *audio = func_to_g_audio(fn); + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + u16 w_length = le16_to_cpu(cr->wLength); + u16 w_index = le16_to_cpu(cr->wIndex); + u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; + u8 control_selector = w_value >> 8; + int value = -EOPNOTSUPP; + + if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_MUTE) { + unsigned int mute; + + u_audio_get_mute(audio, is_playback, &mute); + + *(u8 *)req->buf = mute; + value = min_t(unsigned int, w_length, 1); + } else if (control_selector == UAC_FU_VOLUME) { + __le16 c; + s16 volume; + + u_audio_get_volume(audio, is_playback, &volume); + + c = cpu_to_le16(volume); + + value = min_t(unsigned int, w_length, sizeof(c)); + memcpy(req->buf, &c, value); + } else { + dev_err(&audio->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } + + return value; +} + +static int +in_rq_min(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct g_audio *audio = func_to_g_audio(fn); + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + u16 w_length = le16_to_cpu(cr->wLength); + u16 w_index = le16_to_cpu(cr->wIndex); + u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; + u8 control_selector = w_value >> 8; + int value = -EOPNOTSUPP; + + if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_VOLUME) { + __le16 r; + s16 min_db; + + if (is_playback) + min_db = opts->p_volume_min; + else + min_db = opts->c_volume_min; + + r = cpu_to_le16(min_db); + + value = min_t(unsigned int, w_length, sizeof(r)); + memcpy(req->buf, &r, value); + } else { + dev_err(&audio->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } + + return value; +} + +static int +in_rq_max(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct g_audio *audio = func_to_g_audio(fn); + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + u16 w_length = le16_to_cpu(cr->wLength); + u16 w_index = le16_to_cpu(cr->wIndex); + u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; + u8 control_selector = w_value >> 8; + int value = -EOPNOTSUPP; + + if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_VOLUME) { + __le16 r; + s16 max_db; + + if (is_playback) + max_db = opts->p_volume_max; + else + max_db = opts->c_volume_max; + + r = cpu_to_le16(max_db); + + value = min_t(unsigned int, w_length, sizeof(r)); + memcpy(req->buf, &r, value); + } else { + dev_err(&audio->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } + + return value; +} + +static int +in_rq_res(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct g_audio *audio = func_to_g_audio(fn); + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + u16 w_length = le16_to_cpu(cr->wLength); + u16 w_index = le16_to_cpu(cr->wIndex); + u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; + u8 control_selector = w_value >> 8; + int value = -EOPNOTSUPP; + + if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_VOLUME) { + __le16 r; + s16 res_db; + + if (is_playback) + res_db = opts->p_volume_res; + else + res_db = opts->c_volume_res; + + r = cpu_to_le16(res_db); + + value = min_t(unsigned int, w_length, sizeof(r)); + memcpy(req->buf, &r, value); + } else { + dev_err(&audio->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + } + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } + + return value; +} + +static void +out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct g_audio *audio = req->context; + struct usb_composite_dev *cdev = audio->func.config->cdev; + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + struct f_uac1 *uac1 = func_to_uac1(&audio->func); + struct usb_ctrlrequest *cr = &uac1->setup_cr; + u16 w_index = le16_to_cpu(cr->wIndex); + u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; + u8 control_selector = w_value >> 8; + + if (req->status != 0) { + dev_dbg(&cdev->gadget->dev, "completion err %d\n", req->status); + return; + } + + if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + unsigned int is_playback = 0; + + if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) + is_playback = 1; + + if (control_selector == UAC_FU_MUTE) { + u8 mute = *(u8 *)req->buf; + + u_audio_set_mute(audio, is_playback, mute); + + return; + } else if (control_selector == UAC_FU_VOLUME) { + __le16 *c = req->buf; + s16 volume; + + volume = le16_to_cpu(*c); + u_audio_set_volume(audio, is_playback, volume); + + return; + } else { + dev_err(&audio->gadget->dev, + "%s:%d control_selector=%d TODO!\n", + __func__, __LINE__, control_selector); + usb_ep_set_halt(ep); + } + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + usb_ep_set_halt(ep); + + } +} + +static int +out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) +{ + struct usb_request *req = fn->config->cdev->req; + struct g_audio *audio = func_to_g_audio(fn); + struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio); + struct f_uac1 *uac1 = func_to_uac1(&audio->func); + u16 w_length = le16_to_cpu(cr->wLength); + u16 w_index = le16_to_cpu(cr->wIndex); + u16 w_value = le16_to_cpu(cr->wValue); + u8 entity_id = (w_index >> 8) & 0xff; + u8 control_selector = w_value >> 8; + + if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || + (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { + memcpy(&uac1->setup_cr, cr, sizeof(*cr)); + req->context = audio; + req->complete = out_rq_cur_complete; + + return w_length; + } else { + dev_err(&audio->gadget->dev, + "%s:%d entity_id=%d control_selector=%d TODO!\n", + __func__, __LINE__, entity_id, control_selector); + } + return -EOPNOTSUPP; +} + +static int ac_rq_in(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + int value = -EOPNOTSUPP; + u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case UAC_GET_CUR: + return in_rq_cur(f, ctrl); + case UAC_GET_MIN: + return in_rq_min(f, ctrl); + case UAC_GET_MAX: + return in_rq_max(f, ctrl); + case UAC_GET_RES: + return in_rq_res(f, ctrl); + case UAC_GET_MEM: + break; + case UAC_GET_STAT: + value = len; + break; + default: + break; + } + + return value; +} + static int audio_set_endpoint_req(struct usb_function *f, const struct usb_ctrlrequest *ctrl) { @@ -383,7 +788,13 @@ f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: value = audio_get_endpoint_req(f, ctrl); break; - + case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE: + if (ctrl->bRequest == UAC_SET_CUR) + value = out_rq_cur(f, ctrl); + break; + case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE: + value = ac_rq_in(f, ctrl); + break; default: ERROR(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", ctrl->bRequestType, ctrl->bRequest, @@ -411,6 +822,7 @@ static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) struct usb_composite_dev *cdev = f->config->cdev; struct usb_gadget *gadget = cdev->gadget; struct device *dev = &gadget->dev; + struct g_audio *audio = func_to_g_audio(f); struct f_uac1 *uac1 = func_to_uac1(f); int ret = 0; @@ -426,6 +838,14 @@ static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return -EINVAL; } + + /* restart interrupt endpoint */ + if (uac1->int_ep) { + usb_ep_disable(uac1->int_ep); + config_ep_by_speed(gadget, &audio->func, uac1->int_ep); + usb_ep_enable(uac1->int_ep); + } + return 0; } @@ -481,10 +901,33 @@ static void f_audio_disable(struct usb_function *f) u_audio_stop_playback(&uac1->g_audio); u_audio_stop_capture(&uac1->g_audio); + if (uac1->int_ep) + usb_ep_disable(uac1->int_ep); } /*-------------------------------------------------------------------------*/ +static struct uac_feature_unit_descriptor *build_fu_desc(int chmask) +{ + struct uac_feature_unit_descriptor *fu_desc; + int channels = num_channels(chmask); + int fu_desc_size = UAC_DT_FEATURE_UNIT_SIZE(channels); + + fu_desc = kzalloc(fu_desc_size, GFP_KERNEL); + if (!fu_desc) + return NULL; + + fu_desc->bLength = fu_desc_size; + fu_desc->bDescriptorType = USB_DT_CS_INTERFACE; + + fu_desc->bDescriptorSubtype = UAC_FEATURE_UNIT; + fu_desc->bControlSize = 2; + /* bUnitID, bSourceID and bmaControls will be defined later */ + + return fu_desc; +} + +/* B.3.2 Class-Specific AC Interface Descriptor */ static struct uac1_ac_header_descriptor *build_ac_header_desc(struct f_uac1_opts *opts) { @@ -530,9 +973,23 @@ static void setup_descriptor(struct f_uac1_opts *opts) io_out_ot_desc.bTerminalID = i++; if (EPIN_EN(opts)) usb_in_ot_desc.bTerminalID = i++; - - usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID; - io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID; + if (FUOUT_EN(opts)) + out_feature_unit_desc->bUnitID = i++; + if (FUIN_EN(opts)) + in_feature_unit_desc->bUnitID = i++; + + if (FUIN_EN(opts)) { + usb_in_ot_desc.bSourceID = in_feature_unit_desc->bUnitID; + in_feature_unit_desc->bSourceID = io_in_it_desc.bTerminalID; + } else { + usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID; + } + if (FUOUT_EN(opts)) { + io_out_ot_desc.bSourceID = out_feature_unit_desc->bUnitID; + out_feature_unit_desc->bSourceID = usb_out_it_desc.bTerminalID; + } else { + io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID; + } as_out_header_desc.bTerminalLink = usb_out_it_desc.bTerminalID; as_in_header_desc.bTerminalLink = usb_in_ot_desc.bTerminalID; @@ -544,6 +1001,8 @@ static void setup_descriptor(struct f_uac1_opts *opts) len += sizeof(usb_in_ot_desc); len += sizeof(io_in_it_desc); + if (FUIN_EN(opts)) + len += in_feature_unit_desc->bLength; ac_header_desc->wTotalLength = cpu_to_le16(len); } if (EPOUT_EN(opts)) { @@ -551,6 +1010,8 @@ static void setup_descriptor(struct f_uac1_opts *opts) len += sizeof(usb_out_it_desc); len += sizeof(io_out_ot_desc); + if (FUOUT_EN(opts)) + len += out_feature_unit_desc->bLength; ac_header_desc->wTotalLength = cpu_to_le16(len); } @@ -561,13 +1022,20 @@ static void setup_descriptor(struct f_uac1_opts *opts) if (EPOUT_EN(opts)) { f_audio_desc[i++] = USBDHDR(&usb_out_it_desc); f_audio_desc[i++] = USBDHDR(&io_out_ot_desc); + if (FUOUT_EN(opts)) + f_audio_desc[i++] = USBDHDR(out_feature_unit_desc); } if (EPIN_EN(opts)) { f_audio_desc[i++] = USBDHDR(&io_in_it_desc); f_audio_desc[i++] = USBDHDR(&usb_in_ot_desc); + if (FUIN_EN(opts)) + f_audio_desc[i++] = USBDHDR(in_feature_unit_desc); } + if (FUOUT_EN(opts) || FUIN_EN(opts)) + f_audio_desc[i++] = USBDHDR(&ac_int_ep_desc); + if (EPOUT_EN(opts)) { f_audio_desc[i++] = USBDHDR(&as_out_interface_alt_0_desc); f_audio_desc[i++] = USBDHDR(&as_out_interface_alt_1_desc); @@ -614,6 +1082,28 @@ static int f_audio_validate_opts(struct g_audio *audio, struct device *dev) return -EINVAL; } + if (opts->p_volume_max <= opts->p_volume_min) { + dev_err(dev, "Error: incorrect playback volume max/min\n"); + return -EINVAL; + } else if (opts->c_volume_max <= opts->c_volume_min) { + dev_err(dev, "Error: incorrect capture volume max/min\n"); + return -EINVAL; + } else if (opts->p_volume_res <= 0) { + dev_err(dev, "Error: negative/zero playback volume resolution\n"); + return -EINVAL; + } else if (opts->c_volume_res <= 0) { + dev_err(dev, "Error: negative/zero capture volume resolution\n"); + return -EINVAL; + } + + if ((opts->p_volume_max - opts->p_volume_min) % opts->p_volume_res) { + dev_err(dev, "Error: incorrect playback volume resolution\n"); + return -EINVAL; + } else if ((opts->c_volume_max - opts->c_volume_min) % opts->c_volume_res) { + dev_err(dev, "Error: incorrect capture volume resolution\n"); + return -EINVAL; + } + return 0; } @@ -647,6 +1137,21 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) if (!ac_header_desc) return -ENOMEM; + if (FUOUT_EN(audio_opts)) { + out_feature_unit_desc = build_fu_desc(audio_opts->c_chmask); + if (!out_feature_unit_desc) { + status = -ENOMEM; + goto fail; + } + } + if (FUIN_EN(audio_opts)) { + in_feature_unit_desc = build_fu_desc(audio_opts->p_chmask); + if (!in_feature_unit_desc) { + status = -ENOMEM; + goto err_free_fu; + } + } + ac_interface_desc.iInterface = us[STR_AC_IF].id; usb_out_it_desc.iTerminal = us[STR_USB_OUT_IT].id; usb_out_it_desc.iChannelNames = us[STR_USB_OUT_IT_CH_NAMES].id; @@ -659,6 +1164,21 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) as_in_interface_alt_0_desc.iInterface = us[STR_AS_IN_IF_ALT0].id; as_in_interface_alt_1_desc.iInterface = us[STR_AS_IN_IF_ALT1].id; + if (FUOUT_EN(audio_opts)) { + u8 *i_feature; + + i_feature = (u8 *)out_feature_unit_desc + + out_feature_unit_desc->bLength - 1; + *i_feature = us[STR_FU_OUT].id; + } + if (FUIN_EN(audio_opts)) { + u8 *i_feature; + + i_feature = (u8 *)in_feature_unit_desc + + in_feature_unit_desc->bLength - 1; + *i_feature = us[STR_FU_IN].id; + } + /* Set channel numbers */ usb_out_it_desc.bNrChannels = num_channels(audio_opts->c_chmask); usb_out_it_desc.wChannelConfig = cpu_to_le16(audio_opts->c_chmask); @@ -671,6 +1191,27 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) as_in_type_i_desc.bSubframeSize = audio_opts->p_ssize; as_in_type_i_desc.bBitResolution = audio_opts->p_ssize * 8; + if (FUOUT_EN(audio_opts)) { + __le16 *bma = (__le16 *)&out_feature_unit_desc->bmaControls[0]; + u32 control = 0; + + if (audio_opts->c_mute_present) + control |= UAC_FU_MUTE; + if (audio_opts->c_volume_present) + control |= UAC_FU_VOLUME; + *bma = cpu_to_le16(control); + } + if (FUIN_EN(audio_opts)) { + __le16 *bma = (__le16 *)&in_feature_unit_desc->bmaControls[0]; + u32 control = 0; + + if (audio_opts->p_mute_present) + control |= UAC_FU_MUTE; + if (audio_opts->p_volume_present) + control |= UAC_FU_VOLUME; + *bma = cpu_to_le16(control); + } + /* Set sample rates */ rate = audio_opts->c_srate; sam_freq = as_out_type_i_desc.tSamFreq[0]; @@ -682,7 +1223,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) /* allocate instance-specific interface IDs, and patch descriptors */ status = usb_interface_id(c, f); if (status < 0) - goto fail; + goto err_free_fu; ac_interface_desc.bInterfaceNumber = status; uac1->ac_intf = status; uac1->ac_alt = 0; @@ -692,7 +1233,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) if (EPOUT_EN(audio_opts)) { status = usb_interface_id(c, f); if (status < 0) - goto fail; + goto err_free_fu; as_out_interface_alt_0_desc.bInterfaceNumber = status; as_out_interface_alt_1_desc.bInterfaceNumber = status; ac_header_desc->baInterfaceNr[ba_iface_id++] = status; @@ -703,7 +1244,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) if (EPIN_EN(audio_opts)) { status = usb_interface_id(c, f); if (status < 0) - goto fail; + goto err_free_fu; as_in_interface_alt_0_desc.bInterfaceNumber = status; as_in_interface_alt_1_desc.bInterfaceNumber = status; ac_header_desc->baInterfaceNr[ba_iface_id++] = status; @@ -715,11 +1256,24 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) status = -ENODEV; + ac_interface_desc.bNumEndpoints = 0; + + /* allocate AC interrupt endpoint */ + if (FUOUT_EN(audio_opts) || FUIN_EN(audio_opts)) { + ep = usb_ep_autoconfig(cdev->gadget, &ac_int_ep_desc); + if (!ep) + goto err_free_fu; + uac1->int_ep = ep; + uac1->int_ep->desc = &ac_int_ep_desc; + + ac_interface_desc.bNumEndpoints = 1; + } + /* allocate instance-specific endpoints */ if (EPOUT_EN(audio_opts)) { ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc); if (!ep) - goto fail; + goto err_free_fu; audio->out_ep = ep; audio->out_ep->desc = &as_out_ep_desc; } @@ -727,7 +1281,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) if (EPIN_EN(audio_opts)) { ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc); if (!ep) - goto fail; + goto err_free_fu; audio->in_ep = ep; audio->in_ep->desc = &as_in_ep_desc; } @@ -738,17 +1292,37 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL, NULL); if (status) - goto fail; + goto err_free_fu; audio->out_ep_maxpsize = le16_to_cpu(as_out_ep_desc.wMaxPacketSize); audio->in_ep_maxpsize = le16_to_cpu(as_in_ep_desc.wMaxPacketSize); audio->params.c_chmask = audio_opts->c_chmask; audio->params.c_srate = audio_opts->c_srate; audio->params.c_ssize = audio_opts->c_ssize; + if (FUIN_EN(audio_opts)) { + audio->params.p_fu.id = USB_IN_FU_ID; + audio->params.p_fu.mute_present = audio_opts->p_mute_present; + audio->params.p_fu.volume_present = + audio_opts->p_volume_present; + audio->params.p_fu.volume_min = audio_opts->p_volume_min; + audio->params.p_fu.volume_max = audio_opts->p_volume_max; + audio->params.p_fu.volume_res = audio_opts->p_volume_res; + } audio->params.p_chmask = audio_opts->p_chmask; audio->params.p_srate = audio_opts->p_srate; audio->params.p_ssize = audio_opts->p_ssize; + if (FUOUT_EN(audio_opts)) { + audio->params.c_fu.id = USB_OUT_FU_ID; + audio->params.c_fu.mute_present = audio_opts->c_mute_present; + audio->params.c_fu.volume_present = + audio_opts->c_volume_present; + audio->params.c_fu.volume_min = audio_opts->c_volume_min; + audio->params.c_fu.volume_max = audio_opts->c_volume_max; + audio->params.c_fu.volume_res = audio_opts->c_volume_res; + } audio->params.req_number = audio_opts->req_number; + if (FUOUT_EN(audio_opts) || FUIN_EN(audio_opts)) + audio->notify = audio_notify; status = g_audio_setup(audio, "UAC1_PCM", "UAC1_Gadget"); if (status) @@ -758,6 +1332,11 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) err_card_register: usb_free_all_descriptors(f); +err_free_fu: + kfree(out_feature_unit_desc); + out_feature_unit_desc = NULL; + kfree(in_feature_unit_desc); + in_feature_unit_desc = NULL; fail: kfree(ac_header_desc); ac_header_desc = NULL; @@ -783,7 +1362,15 @@ static struct configfs_item_operations f_uac1_item_ops = { .release = f_uac1_attr_release, }; -#define UAC1_ATTRIBUTE(name) \ +#define uac1_kstrtou32 kstrtou32 +#define uac1_kstrtos16 kstrtos16 +#define uac1_kstrtobool(s, base, res) kstrtobool((s), (res)) + +static const char *u32_fmt = "%u\n"; +static const char *s16_fmt = "%hd\n"; +static const char *bool_fmt = "%u\n"; + +#define UAC1_ATTRIBUTE(type, name) \ static ssize_t f_uac1_opts_##name##_show( \ struct config_item *item, \ char *page) \ @@ -792,7 +1379,7 @@ static ssize_t f_uac1_opts_##name##_show( \ int result; \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%u\n", opts->name); \ + result = sprintf(page, type##_fmt, opts->name); \ mutex_unlock(&opts->lock); \ \ return result; \ @@ -804,7 +1391,7 @@ static ssize_t f_uac1_opts_##name##_store( \ { \ struct f_uac1_opts *opts = to_f_uac1_opts(item); \ int ret; \ - u32 num; \ + type num; \ \ mutex_lock(&opts->lock); \ if (opts->refcnt) { \ @@ -812,7 +1399,7 @@ static ssize_t f_uac1_opts_##name##_store( \ goto end; \ } \ \ - ret = kstrtou32(page, 0, &num); \ + ret = uac1_kstrto##type(page, 0, &num); \ if (ret) \ goto end; \ \ @@ -826,13 +1413,25 @@ end: \ \ CONFIGFS_ATTR(f_uac1_opts_, name) -UAC1_ATTRIBUTE(c_chmask); -UAC1_ATTRIBUTE(c_srate); -UAC1_ATTRIBUTE(c_ssize); -UAC1_ATTRIBUTE(p_chmask); -UAC1_ATTRIBUTE(p_srate); -UAC1_ATTRIBUTE(p_ssize); -UAC1_ATTRIBUTE(req_number); +UAC1_ATTRIBUTE(u32, c_chmask); +UAC1_ATTRIBUTE(u32, c_srate); +UAC1_ATTRIBUTE(u32, c_ssize); +UAC1_ATTRIBUTE(u32, p_chmask); +UAC1_ATTRIBUTE(u32, p_srate); +UAC1_ATTRIBUTE(u32, p_ssize); +UAC1_ATTRIBUTE(u32, req_number); + +UAC1_ATTRIBUTE(bool, p_mute_present); +UAC1_ATTRIBUTE(bool, p_volume_present); +UAC1_ATTRIBUTE(s16, p_volume_min); +UAC1_ATTRIBUTE(s16, p_volume_max); +UAC1_ATTRIBUTE(s16, p_volume_res); + +UAC1_ATTRIBUTE(bool, c_mute_present); +UAC1_ATTRIBUTE(bool, c_volume_present); +UAC1_ATTRIBUTE(s16, c_volume_min); +UAC1_ATTRIBUTE(s16, c_volume_max); +UAC1_ATTRIBUTE(s16, c_volume_res); static struct configfs_attribute *f_uac1_attrs[] = { &f_uac1_opts_attr_c_chmask, @@ -842,6 +1441,19 @@ static struct configfs_attribute *f_uac1_attrs[] = { &f_uac1_opts_attr_p_srate, &f_uac1_opts_attr_p_ssize, &f_uac1_opts_attr_req_number, + + &f_uac1_opts_attr_p_mute_present, + &f_uac1_opts_attr_p_volume_present, + &f_uac1_opts_attr_p_volume_min, + &f_uac1_opts_attr_p_volume_max, + &f_uac1_opts_attr_p_volume_res, + + &f_uac1_opts_attr_c_mute_present, + &f_uac1_opts_attr_c_volume_present, + &f_uac1_opts_attr_c_volume_min, + &f_uac1_opts_attr_c_volume_max, + &f_uac1_opts_attr_c_volume_res, + NULL, }; @@ -879,6 +1491,19 @@ static struct usb_function_instance *f_audio_alloc_inst(void) opts->p_chmask = UAC1_DEF_PCHMASK; opts->p_srate = UAC1_DEF_PSRATE; opts->p_ssize = UAC1_DEF_PSSIZE; + + opts->p_mute_present = UAC1_DEF_MUTE_PRESENT; + opts->p_volume_present = UAC1_DEF_VOLUME_PRESENT; + opts->p_volume_min = UAC1_DEF_MIN_DB; + opts->p_volume_max = UAC1_DEF_MAX_DB; + opts->p_volume_res = UAC1_DEF_RES_DB; + + opts->c_mute_present = UAC1_DEF_MUTE_PRESENT; + opts->c_volume_present = UAC1_DEF_VOLUME_PRESENT; + opts->c_volume_min = UAC1_DEF_MIN_DB; + opts->c_volume_max = UAC1_DEF_MAX_DB; + opts->c_volume_res = UAC1_DEF_RES_DB; + opts->req_number = UAC1_DEF_REQ_NUM; return &opts->func_inst; } @@ -903,6 +1528,11 @@ static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f) g_audio_cleanup(audio); usb_free_all_descriptors(f); + kfree(out_feature_unit_desc); + out_feature_unit_desc = NULL; + kfree(in_feature_unit_desc); + in_feature_unit_desc = NULL; + kfree(ac_header_desc); ac_header_desc = NULL; diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h index 39c0e29e1b46..589fae861141 100644 --- a/drivers/usb/gadget/function/u_uac1.h +++ b/drivers/usb/gadget/function/u_uac1.h @@ -18,6 +18,13 @@ #define UAC1_DEF_PSRATE 48000 #define UAC1_DEF_PSSIZE 2 #define UAC1_DEF_REQ_NUM 2 +#define UAC1_DEF_INT_REQ_NUM 10 + +#define UAC1_DEF_MUTE_PRESENT 1 +#define UAC1_DEF_VOLUME_PRESENT 1 +#define UAC1_DEF_MIN_DB (-100*256) /* -100 dB */ +#define UAC1_DEF_MAX_DB 0 /* 0 dB */ +#define UAC1_DEF_RES_DB (1*256) /* 1 dB */ struct f_uac1_opts { @@ -28,6 +35,19 @@ struct f_uac1_opts { int p_chmask; int p_srate; int p_ssize; + + bool p_mute_present; + bool p_volume_present; + s16 p_volume_min; + s16 p_volume_max; + s16 p_volume_res; + + bool c_mute_present; + bool c_volume_present; + s16 c_volume_min; + s16 c_volume_max; + s16 c_volume_res; + int req_number; unsigned bound:1; -- cgit v1.2.3 From 8e6cb5d27e8246d9c986ec162d066a502d2b602b Mon Sep 17 00:00:00 2001 From: Wesley Cheng Date: Sun, 4 Jul 2021 02:33:12 +0100 Subject: usb: dwc3: dwc3-qcom: Fix typo in the dwc3 vbus override API There was an extra character in the dwc3_qcom_vbus_override_enable() function. Removed the extra character. Signed-off-by: Wesley Cheng Signed-off-by: Bryan O'Donoghue Reviewed-by: Bjorn Andersson Link: https://lore.kernel.org/r/20210704013314.200951-2-bryan.odonoghue@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 2223b59e278f..25cd123e441a 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -115,7 +115,7 @@ static inline void dwc3_qcom_clrbits(void __iomem *base, u32 offset, u32 val) readl(base + offset); } -static void dwc3_qcom_vbus_overrride_enable(struct dwc3_qcom *qcom, bool enable) +static void dwc3_qcom_vbus_override_enable(struct dwc3_qcom *qcom, bool enable) { if (enable) { dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL, @@ -136,7 +136,7 @@ static int dwc3_qcom_vbus_notifier(struct notifier_block *nb, struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, vbus_nb); /* enable vbus override for device mode */ - dwc3_qcom_vbus_overrride_enable(qcom, event); + dwc3_qcom_vbus_override_enable(qcom, event); qcom->mode = event ? USB_DR_MODE_PERIPHERAL : USB_DR_MODE_HOST; return NOTIFY_DONE; @@ -148,7 +148,7 @@ static int dwc3_qcom_host_notifier(struct notifier_block *nb, struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, host_nb); /* disable vbus override in host mode */ - dwc3_qcom_vbus_overrride_enable(qcom, !event); + dwc3_qcom_vbus_override_enable(qcom, !event); qcom->mode = event ? USB_DR_MODE_HOST : USB_DR_MODE_PERIPHERAL; return NOTIFY_DONE; @@ -826,7 +826,7 @@ static int dwc3_qcom_probe(struct platform_device *pdev) /* enable vbus override for device mode */ if (qcom->mode == USB_DR_MODE_PERIPHERAL) - dwc3_qcom_vbus_overrride_enable(qcom, true); + dwc3_qcom_vbus_override_enable(qcom, true); /* register extcon to override sw_vbus on Vbus change later */ ret = dwc3_qcom_register_extcon(qcom); -- cgit v1.2.3 From 2037f2991ddedded1ef4aaabe4caf11e306158dc Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Thu, 15 Jul 2021 17:07:50 +0800 Subject: usb: common: add helper to get role-switch-default-mode Add helper to get "role-switch-default-mode", and convert it to the corresponding enum usb_dr_mode Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1626340078-29111-6-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/common/common.c | 20 ++++++++++++++++++++ include/linux/usb/otg.h | 1 + 2 files changed, 21 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/common/common.c b/drivers/usb/common/common.c index 347fb3d3894a..c9bdeb4ddcb5 100644 --- a/drivers/usb/common/common.c +++ b/drivers/usb/common/common.c @@ -200,6 +200,26 @@ enum usb_dr_mode usb_get_dr_mode(struct device *dev) } EXPORT_SYMBOL_GPL(usb_get_dr_mode); +/** + * usb_get_role_switch_default_mode - Get default mode for given device + * @dev: Pointer to the given device + * + * The function gets string from property 'role-switch-default-mode', + * and returns the corresponding enum usb_dr_mode. + */ +enum usb_dr_mode usb_get_role_switch_default_mode(struct device *dev) +{ + const char *str; + int ret; + + ret = device_property_read_string(dev, "role-switch-default-mode", &str); + if (ret < 0) + return USB_DR_MODE_UNKNOWN; + + return usb_get_dr_mode_from_string(str); +} +EXPORT_SYMBOL_GPL(usb_get_role_switch_default_mode); + /** * usb_decode_interval - Decode bInterval into the time expressed in 1us unit * @epd: The descriptor of the endpoint diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h index 7ceeecbb9e02..6475f880be37 100644 --- a/include/linux/usb/otg.h +++ b/include/linux/usb/otg.h @@ -128,5 +128,6 @@ enum usb_dr_mode { * and returns the corresponding enum usb_dr_mode */ extern enum usb_dr_mode usb_get_dr_mode(struct device *dev); +extern enum usb_dr_mode usb_get_role_switch_default_mode(struct device *dev); #endif /* __LINUX_USB_OTG_H */ -- cgit v1.2.3 From 26f94fe8e739641e96c50f70eb6e78ef1693d9ca Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Thu, 15 Jul 2021 17:07:51 +0800 Subject: usb: dwc3: drd: use helper to get role-switch-default-mode Use the new helper usb_get_role_switch_default_mode() to get property of "role-switch-default-mode" Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1626340078-29111-7-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/drd.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index 8fcbac10510c..d7f76835137f 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -541,14 +541,10 @@ static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw) static int dwc3_setup_role_switch(struct dwc3 *dwc) { struct usb_role_switch_desc dwc3_role_switch = {NULL}; - const char *str; u32 mode; - int ret; - ret = device_property_read_string(dwc->dev, "role-switch-default-mode", - &str); - if (ret >= 0 && !strncmp(str, "host", strlen("host"))) { - dwc->role_switch_default_mode = USB_DR_MODE_HOST; + dwc->role_switch_default_mode = usb_get_role_switch_default_mode(dwc->dev); + if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) { mode = DWC3_GCTL_PRTCAP_HOST; } else { dwc->role_switch_default_mode = USB_DR_MODE_PERIPHERAL; -- cgit v1.2.3 From 88c6b90188d8ebade3f5b1f80b51aa64e55de27e Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Thu, 15 Jul 2021 17:07:52 +0800 Subject: usb: mtu3: support property role-switch-default-mode Support default mode config when use usb-role-switch Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1626340078-29111-8-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3.h | 2 ++ drivers/usb/mtu3/mtu3_dr.c | 24 ++++++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3.h b/drivers/usb/mtu3/mtu3.h index 5546b868b08b..bc82c7f97ad6 100644 --- a/drivers/usb/mtu3/mtu3.h +++ b/drivers/usb/mtu3/mtu3.h @@ -199,6 +199,7 @@ struct mtu3_gpd_ring { * @id_nb : notifier for iddig(idpin) detection * @dr_work : work for drd mode switch, used to avoid sleep in atomic context * @desired_role : role desired to switch +* @default_role : default mode while usb role is USB_ROLE_NONE * @role_sw : use USB Role Switch to support dual-role switch, can't use * extcon at the same time, and extcon is deprecated. * @role_sw_used : true when the USB Role Switch is used. @@ -212,6 +213,7 @@ struct otg_switch_mtk { struct notifier_block id_nb; struct work_struct dr_work; enum usb_role desired_role; + enum usb_role default_role; struct usb_role_switch *role_sw; bool role_sw_used; bool is_u3_drd; diff --git a/drivers/usb/mtu3/mtu3_dr.c b/drivers/usb/mtu3/mtu3_dr.c index 318fbc618137..30e7e5fc0f88 100644 --- a/drivers/usb/mtu3/mtu3_dr.c +++ b/drivers/usb/mtu3/mtu3_dr.c @@ -137,8 +137,12 @@ static void ssusb_mode_sw_work(struct work_struct *work) current_role = ssusb->is_host ? USB_ROLE_HOST : USB_ROLE_DEVICE; - if (desired_role == USB_ROLE_NONE) + if (desired_role == USB_ROLE_NONE) { + /* the default mode is host as probe does */ desired_role = USB_ROLE_HOST; + if (otg_sx->default_role == USB_ROLE_DEVICE) + desired_role = USB_ROLE_DEVICE; + } if (current_role == desired_role) return; @@ -274,17 +278,29 @@ static int ssusb_role_sw_register(struct otg_switch_mtk *otg_sx) { struct usb_role_switch_desc role_sx_desc = { 0 }; struct ssusb_mtk *ssusb = otg_sx_to_ssusb(otg_sx); + struct device *dev = ssusb->dev; + enum usb_dr_mode mode; if (!otg_sx->role_sw_used) return 0; + mode = usb_get_role_switch_default_mode(dev); + if (mode == USB_DR_MODE_PERIPHERAL) + otg_sx->default_role = USB_ROLE_DEVICE; + else + otg_sx->default_role = USB_ROLE_HOST; + role_sx_desc.set = ssusb_role_sw_set; role_sx_desc.get = ssusb_role_sw_get; - role_sx_desc.fwnode = dev_fwnode(ssusb->dev); + role_sx_desc.fwnode = dev_fwnode(dev); role_sx_desc.driver_data = ssusb; - otg_sx->role_sw = usb_role_switch_register(ssusb->dev, &role_sx_desc); + otg_sx->role_sw = usb_role_switch_register(dev, &role_sx_desc); + if (IS_ERR(otg_sx->role_sw)) + return PTR_ERR(otg_sx->role_sw); - return PTR_ERR_OR_ZERO(otg_sx->role_sw); + ssusb_set_mode(otg_sx, otg_sx->default_role); + + return 0; } int ssusb_otg_switch_init(struct ssusb_mtk *ssusb) -- cgit v1.2.3 From d7e127242816e06981840880eead898197a3257b Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Thu, 15 Jul 2021 17:07:53 +0800 Subject: usb: mtu3: support option to disable usb2 ports Add support to disable specific usb2 host ports, it's useful when a usb2 port is disabled on some platforms, but enabled on others for the same SoC. Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1626340078-29111-9-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3.h | 4 ++++ drivers/usb/mtu3/mtu3_host.c | 8 +++++++- drivers/usb/mtu3/mtu3_plat.c | 11 +++++++++-- 3 files changed, 20 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3.h b/drivers/usb/mtu3/mtu3.h index bc82c7f97ad6..0ae9b33b50ea 100644 --- a/drivers/usb/mtu3/mtu3.h +++ b/drivers/usb/mtu3/mtu3.h @@ -228,6 +228,9 @@ struct otg_switch_mtk { * host only, device only or dual-role mode * @u2_ports: number of usb2.0 host ports * @u3_ports: number of usb3.0 host ports + * @u2p_dis_msk: mask of disabling usb2 ports, e.g. bit0==1 to + * disable u2port0, bit1==1 to disable u2port1,... etc, + * but when use dual-role mode, can't disable u2port0 * @u3p_dis_msk: mask of disabling usb3 ports, for example, bit0==1 to * disable u3port0, bit1==1 to disable u3port1,... etc * @dbgfs_root: only used when supports manual dual-role switch via debugfs @@ -252,6 +255,7 @@ struct ssusb_mtk { bool is_host; int u2_ports; int u3_ports; + int u2p_dis_msk; int u3p_dis_msk; struct dentry *dbgfs_root; /* usb wakeup for host mode */ diff --git a/drivers/usb/mtu3/mtu3_host.c b/drivers/usb/mtu3/mtu3_host.c index 93a1a4c11e1e..6185bac5fecf 100644 --- a/drivers/usb/mtu3/mtu3_host.c +++ b/drivers/usb/mtu3/mtu3_host.c @@ -155,6 +155,9 @@ int ssusb_host_enable(struct ssusb_mtk *ssusb) /* power on and enable all u2 ports */ for (i = 0; i < num_u2p; i++) { + if ((0x1 << i) & ssusb->u2p_dis_msk) + continue; + value = mtu3_readl(ibase, SSUSB_U2_CTRL(i)); value &= ~(SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS); value |= SSUSB_U2_PORT_HOST_SEL; @@ -188,8 +191,11 @@ int ssusb_host_disable(struct ssusb_mtk *ssusb, bool suspend) mtu3_writel(ibase, SSUSB_U3_CTRL(i), value); } - /* power down and disable all u2 ports */ + /* power down and disable u2 ports except skipped ones */ for (i = 0; i < num_u2p; i++) { + if ((0x1 << i) & ssusb->u2p_dis_msk) + continue; + value = mtu3_readl(ibase, SSUSB_U2_CTRL(i)); value |= SSUSB_U2_PORT_PDN; value |= suspend ? 0 : SSUSB_U2_PORT_DIS; diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index c0615f6e5cce..5162b9988dde 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -225,6 +225,8 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb) /* optional property, ignore the error if it does not exist */ of_property_read_u32(node, "mediatek,u3p-dis-msk", &ssusb->u3p_dis_msk); + of_property_read_u32(node, "mediatek,u2p-dis-msk", + &ssusb->u2p_dis_msk); otg_sx->vbus = devm_regulator_get(dev, "vbus"); if (IS_ERR(otg_sx->vbus)) { @@ -241,6 +243,9 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb) of_property_read_bool(node, "enable-manual-drd"); otg_sx->role_sw_used = of_property_read_bool(node, "usb-role-switch"); + /* can't disable port0 when use dual-role mode */ + ssusb->u2p_dis_msk &= ~0x1; + if (otg_sx->role_sw_used || otg_sx->manual_drd_enabled) goto out; @@ -253,9 +258,11 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb) } out: - dev_info(dev, "dr_mode: %d, is_u3_dr: %d, u3p_dis_msk: %x, drd: %s\n", - ssusb->dr_mode, otg_sx->is_u3_drd, ssusb->u3p_dis_msk, + dev_info(dev, "dr_mode: %d, is_u3_dr: %d, drd: %s\n", + ssusb->dr_mode, otg_sx->is_u3_drd, otg_sx->manual_drd_enabled ? "manual" : "auto"); + dev_info(dev, "u2p_dis_msk: %x, u3p_dis_msk: %x\n", + ssusb->u2p_dis_msk, ssusb->u3p_dis_msk); return 0; } -- cgit v1.2.3 From 0609c1aa10de07b8828cac88bf26140ec1a56a51 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Thu, 15 Jul 2021 17:07:54 +0800 Subject: usb: mtu3: add new helpers for host suspend/resume Extract two helpers for host suspend and resume, will make it easy to support dual-role mode suspend/resume later. Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1626340078-29111-10-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_dr.h | 8 ++-- drivers/usb/mtu3/mtu3_host.c | 92 +++++++++++++++++++++++++++++++++++++++----- drivers/usb/mtu3/mtu3_plat.c | 4 +- 3 files changed, 88 insertions(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3_dr.h b/drivers/usb/mtu3/mtu3_dr.h index 760fe7d69c6b..70dbf4706138 100644 --- a/drivers/usb/mtu3/mtu3_dr.h +++ b/drivers/usb/mtu3/mtu3_dr.h @@ -16,8 +16,8 @@ int ssusb_host_init(struct ssusb_mtk *ssusb, struct device_node *parent_dn); void ssusb_host_exit(struct ssusb_mtk *ssusb); int ssusb_wakeup_of_property_parse(struct ssusb_mtk *ssusb, struct device_node *dn); -int ssusb_host_enable(struct ssusb_mtk *ssusb); -int ssusb_host_disable(struct ssusb_mtk *ssusb, bool suspend); +int ssusb_host_resume(struct ssusb_mtk *ssusb, bool p0_skipped); +int ssusb_host_suspend(struct ssusb_mtk *ssusb); void ssusb_wakeup_set(struct ssusb_mtk *ssusb, bool enable); #else @@ -38,12 +38,12 @@ static inline int ssusb_wakeup_of_property_parse( return 0; } -static inline int ssusb_host_enable(struct ssusb_mtk *ssusb) +static inline int ssusb_host_resume(struct ssusb_mtk *ssusb, bool p0_skipped) { return 0; } -static inline int ssusb_host_disable(struct ssusb_mtk *ssusb, bool suspend) +static inline int ssusb_host_suspend(struct ssusb_mtk *ssusb) { return 0; } diff --git a/drivers/usb/mtu3/mtu3_host.c b/drivers/usb/mtu3/mtu3_host.c index 6185bac5fecf..a0a6a181b752 100644 --- a/drivers/usb/mtu3/mtu3_host.c +++ b/drivers/usb/mtu3/mtu3_host.c @@ -126,7 +126,7 @@ static void host_ports_num_get(struct ssusb_mtk *ssusb) } /* only configure ports will be used later */ -int ssusb_host_enable(struct ssusb_mtk *ssusb) +static int ssusb_host_enable(struct ssusb_mtk *ssusb) { void __iomem *ibase = ssusb->ippc_base; int num_u3p = ssusb->u3_ports; @@ -171,13 +171,12 @@ int ssusb_host_enable(struct ssusb_mtk *ssusb) return ssusb_check_clocks(ssusb, check_clk); } -int ssusb_host_disable(struct ssusb_mtk *ssusb, bool suspend) +static int ssusb_host_disable(struct ssusb_mtk *ssusb) { void __iomem *ibase = ssusb->ippc_base; int num_u3p = ssusb->u3_ports; int num_u2p = ssusb->u2_ports; u32 value; - int ret; int i; /* power down and disable u3 ports except skipped ones */ @@ -186,8 +185,7 @@ int ssusb_host_disable(struct ssusb_mtk *ssusb, bool suspend) continue; value = mtu3_readl(ibase, SSUSB_U3_CTRL(i)); - value |= SSUSB_U3_PORT_PDN; - value |= suspend ? 0 : SSUSB_U3_PORT_DIS; + value |= SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS; mtu3_writel(ibase, SSUSB_U3_CTRL(i), value); } @@ -197,16 +195,90 @@ int ssusb_host_disable(struct ssusb_mtk *ssusb, bool suspend) continue; value = mtu3_readl(ibase, SSUSB_U2_CTRL(i)); - value |= SSUSB_U2_PORT_PDN; - value |= suspend ? 0 : SSUSB_U2_PORT_DIS; + value |= SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS; mtu3_writel(ibase, SSUSB_U2_CTRL(i), value); } /* power down host ip */ mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN); - if (!suspend) - return 0; + return 0; +} + +int ssusb_host_resume(struct ssusb_mtk *ssusb, bool p0_skipped) +{ + void __iomem *ibase = ssusb->ippc_base; + int u3p_skip_msk = ssusb->u3p_dis_msk; + int u2p_skip_msk = ssusb->u2p_dis_msk; + int num_u3p = ssusb->u3_ports; + int num_u2p = ssusb->u2_ports; + u32 value; + int i; + + if (p0_skipped) { + u2p_skip_msk |= 0x1; + if (ssusb->otg_switch.is_u3_drd) + u3p_skip_msk |= 0x1; + } + + /* power on host ip */ + mtu3_clrbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN); + + /* power on u3 ports except skipped ones */ + for (i = 0; i < num_u3p; i++) { + if ((0x1 << i) & u3p_skip_msk) + continue; + + value = mtu3_readl(ibase, SSUSB_U3_CTRL(i)); + value &= ~SSUSB_U3_PORT_PDN; + mtu3_writel(ibase, SSUSB_U3_CTRL(i), value); + } + + /* power on all u2 ports except skipped ones */ + for (i = 0; i < num_u2p; i++) { + if ((0x1 << i) & u2p_skip_msk) + continue; + + value = mtu3_readl(ibase, SSUSB_U2_CTRL(i)); + value &= ~SSUSB_U2_PORT_PDN; + mtu3_writel(ibase, SSUSB_U2_CTRL(i), value); + } + + return 0; +} + +/* here not skip port0 due to PDN can be set repeatedly */ +int ssusb_host_suspend(struct ssusb_mtk *ssusb) +{ + void __iomem *ibase = ssusb->ippc_base; + int num_u3p = ssusb->u3_ports; + int num_u2p = ssusb->u2_ports; + u32 value; + int ret; + int i; + + /* power down u3 ports except skipped ones */ + for (i = 0; i < num_u3p; i++) { + if ((0x1 << i) & ssusb->u3p_dis_msk) + continue; + + value = mtu3_readl(ibase, SSUSB_U3_CTRL(i)); + value |= SSUSB_U3_PORT_PDN; + mtu3_writel(ibase, SSUSB_U3_CTRL(i), value); + } + + /* power down u2 ports except skipped ones */ + for (i = 0; i < num_u2p; i++) { + if ((0x1 << i) & ssusb->u2p_dis_msk) + continue; + + value = mtu3_readl(ibase, SSUSB_U2_CTRL(i)); + value |= SSUSB_U2_PORT_PDN; + mtu3_writel(ibase, SSUSB_U2_CTRL(i), value); + } + + /* power down host ip */ + mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN); /* wait for host ip to sleep */ ret = readl_poll_timeout(ibase + U3D_SSUSB_IP_PW_STS1, value, @@ -237,7 +309,7 @@ static void ssusb_host_cleanup(struct ssusb_mtk *ssusb) if (ssusb->is_host) ssusb_set_vbus(&ssusb->otg_switch, 0); - ssusb_host_disable(ssusb, false); + ssusb_host_disable(ssusb); } /* diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index 5162b9988dde..a906b24723e6 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -411,7 +411,7 @@ static int __maybe_unused mtu3_suspend(struct device *dev) if (!ssusb->is_host) return 0; - ssusb_host_disable(ssusb, true); + ssusb_host_suspend(ssusb); ssusb_phy_power_off(ssusb); clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks); ssusb_wakeup_set(ssusb, true); @@ -438,7 +438,7 @@ static int __maybe_unused mtu3_resume(struct device *dev) if (ret) goto phy_err; - ssusb_host_enable(ssusb); + ssusb_host_resume(ssusb, false); return 0; -- cgit v1.2.3 From fa6f59e28c6197150d9aa94f0d617b59090691e5 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Thu, 15 Jul 2021 17:07:55 +0800 Subject: usb: mtu3: support runtime PM for host mode Use a dedicated wakeup irq for runtime suspend/resume, and interrupts names are provided if using wakeup irq, this patch only support host mode. Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1626340078-29111-11-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3.h | 1 + drivers/usb/mtu3/mtu3_core.c | 13 ++++++--- drivers/usb/mtu3/mtu3_plat.c | 64 +++++++++++++++++++++++++++++++++++++++----- 3 files changed, 69 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3.h b/drivers/usb/mtu3/mtu3.h index 0ae9b33b50ea..171e5b383063 100644 --- a/drivers/usb/mtu3/mtu3.h +++ b/drivers/usb/mtu3/mtu3.h @@ -246,6 +246,7 @@ struct ssusb_mtk { void __iomem *ippc_base; struct phy **phys; int num_phys; + int wakeup_irq; /* common power & clock */ struct regulator *vusb33; struct clk_bulk_data clks[BULK_CLKS_CNT]; diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c index 562f4357831e..6d23acb4fffc 100644 --- a/drivers/usb/mtu3/mtu3_core.c +++ b/drivers/usb/mtu3/mtu3_core.c @@ -888,9 +888,16 @@ int ssusb_gadget_init(struct ssusb_mtk *ssusb) if (mtu == NULL) return -ENOMEM; - mtu->irq = platform_get_irq(pdev, 0); - if (mtu->irq < 0) - return mtu->irq; + mtu->irq = platform_get_irq_byname_optional(pdev, "device"); + if (mtu->irq < 0) { + if (mtu->irq == -EPROBE_DEFER) + return mtu->irq; + + /* for backward compatibility */ + mtu->irq = platform_get_irq(pdev, 0); + if (mtu->irq < 0) + return mtu->irq; + } dev_info(dev, "irq %d\n", mtu->irq); mtu->mac_base = devm_platform_ioremap_resource_byname(pdev, "mac"); diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index a906b24723e6..2be890f84c94 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "mtu3.h" #include "mtu3_dr.h" @@ -208,6 +209,10 @@ static int get_ssusb_rscs(struct platform_device *pdev, struct ssusb_mtk *ssusb) if (IS_ERR(ssusb->ippc_base)) return PTR_ERR(ssusb->ippc_base); + ssusb->wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup"); + if (ssusb->wakeup_irq == -EPROBE_DEFER) + return ssusb->wakeup_irq; + ssusb->dr_mode = usb_get_dr_mode(dev); if (ssusb->dr_mode == USB_DR_MODE_UNKNOWN) ssusb->dr_mode = USB_DR_MODE_OTG; @@ -295,14 +300,25 @@ static int mtu3_probe(struct platform_device *pdev) ssusb_debugfs_create_root(ssusb); /* enable power domain */ + pm_runtime_set_active(dev); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, 4000); pm_runtime_enable(dev); pm_runtime_get_sync(dev); - device_enable_async_suspend(dev); ret = ssusb_rscs_init(ssusb); if (ret) goto comm_init_err; + if (ssusb->wakeup_irq > 0) { + ret = dev_pm_set_dedicated_wake_irq(dev, ssusb->wakeup_irq); + if (ret) { + dev_err(dev, "failed to set wakeup irq %d\n", ssusb->wakeup_irq); + goto comm_exit; + } + dev_info(dev, "wakeup irq %d\n", ssusb->wakeup_irq); + } + ssusb_ip_sw_reset(ssusb); if (IS_ENABLED(CONFIG_USB_MTU3_HOST)) @@ -353,6 +369,11 @@ static int mtu3_probe(struct platform_device *pdev) goto comm_exit; } + device_enable_async_suspend(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + pm_runtime_forbid(dev); + return 0; host_exit: @@ -362,7 +383,7 @@ gadget_exit: comm_exit: ssusb_rscs_exit(ssusb); comm_init_err: - pm_runtime_put_sync(dev); + pm_runtime_put_noidle(dev); pm_runtime_disable(dev); ssusb_debugfs_remove_root(ssusb); @@ -373,6 +394,8 @@ static int mtu3_remove(struct platform_device *pdev) { struct ssusb_mtk *ssusb = platform_get_drvdata(pdev); + pm_runtime_get_sync(&pdev->dev); + switch (ssusb->dr_mode) { case USB_DR_MODE_PERIPHERAL: ssusb_gadget_exit(ssusb); @@ -390,9 +413,10 @@ static int mtu3_remove(struct platform_device *pdev) } ssusb_rscs_exit(ssusb); - pm_runtime_put_sync(&pdev->dev); - pm_runtime_disable(&pdev->dev); ssusb_debugfs_remove_root(ssusb); + pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); return 0; } @@ -401,7 +425,7 @@ static int mtu3_remove(struct platform_device *pdev) * when support dual-role mode, we reject suspend when * it works as device mode; */ -static int __maybe_unused mtu3_suspend(struct device *dev) +static int mtu3_suspend_common(struct device *dev, pm_message_t msg) { struct ssusb_mtk *ssusb = dev_get_drvdata(dev); @@ -419,7 +443,7 @@ static int __maybe_unused mtu3_suspend(struct device *dev) return 0; } -static int __maybe_unused mtu3_resume(struct device *dev) +static int mtu3_resume_common(struct device *dev, pm_message_t msg) { struct ssusb_mtk *ssusb = dev_get_drvdata(dev); int ret; @@ -448,8 +472,36 @@ clks_err: return ret; } +static int __maybe_unused mtu3_suspend(struct device *dev) +{ + return mtu3_suspend_common(dev, PMSG_SUSPEND); +} + +static int __maybe_unused mtu3_resume(struct device *dev) +{ + return mtu3_resume_common(dev, PMSG_SUSPEND); +} + +static int __maybe_unused mtu3_runtime_suspend(struct device *dev) +{ + if (!device_may_wakeup(dev)) + return 0; + + return mtu3_suspend_common(dev, PMSG_AUTO_SUSPEND); +} + +static int __maybe_unused mtu3_runtime_resume(struct device *dev) +{ + if (!device_may_wakeup(dev)) + return 0; + + return mtu3_resume_common(dev, PMSG_AUTO_SUSPEND); +} + static const struct dev_pm_ops mtu3_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(mtu3_suspend, mtu3_resume) + SET_RUNTIME_PM_OPS(mtu3_runtime_suspend, + mtu3_runtime_resume, NULL) }; #define DEV_PM_OPS (IS_ENABLED(CONFIG_PM) ? &mtu3_pm_ops : NULL) -- cgit v1.2.3 From 6244831543ec269f40ed9032bb8194c089049718 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Thu, 15 Jul 2021 17:07:56 +0800 Subject: usb: mtu3: add helper to power on/down device Add helper to power on/down device ports and ip, it will be used when support device suspend/resume Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1626340078-29111-12-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_core.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c index 6d23acb4fffc..648e970d77ba 100644 --- a/drivers/usb/mtu3/mtu3_core.c +++ b/drivers/usb/mtu3/mtu3_core.c @@ -141,6 +141,28 @@ static void mtu3_device_disable(struct mtu3 *mtu) mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN); } +static void mtu3_dev_power_on(struct mtu3 *mtu) +{ + void __iomem *ibase = mtu->ippc_base; + + mtu3_clrbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN); + if (mtu->is_u3_ip) + mtu3_clrbits(ibase, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_PDN); + + mtu3_clrbits(ibase, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_PDN); +} + +static void mtu3_dev_power_down(struct mtu3 *mtu) +{ + void __iomem *ibase = mtu->ippc_base; + + if (mtu->is_u3_ip) + mtu3_setbits(ibase, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_PDN); + + mtu3_setbits(ibase, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_PDN); + mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN); +} + /* reset U3D's device module. */ static void mtu3_device_reset(struct mtu3 *mtu) { @@ -333,12 +355,7 @@ void mtu3_start(struct mtu3 *mtu) dev_dbg(mtu->dev, "%s devctl 0x%x\n", __func__, mtu3_readl(mbase, U3D_DEVICE_CONTROL)); - mtu3_clrbits(mtu->ippc_base, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN); - if (mtu->is_u3_ip) - mtu3_clrbits(mtu->ippc_base, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_PDN); - - mtu3_clrbits(mtu->ippc_base, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_PDN); - + mtu3_dev_power_on(mtu); mtu3_csr_init(mtu); mtu3_set_speed(mtu, mtu->speed); @@ -360,12 +377,7 @@ void mtu3_stop(struct mtu3 *mtu) mtu3_dev_on_off(mtu, 0); mtu->is_active = 0; - - if (mtu->is_u3_ip) - mtu3_setbits(mtu->ippc_base, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_PDN); - - mtu3_setbits(mtu->ippc_base, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_PDN); - mtu3_setbits(mtu->ippc_base, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN); + mtu3_dev_power_down(mtu); } /* for non-ep0 */ -- cgit v1.2.3 From 427c66422e14b8468ee005aa6edf76ef0c2a8fc2 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Thu, 15 Jul 2021 17:07:57 +0800 Subject: usb: mtu3: support suspend/resume for device mode Support suspend/resume for device mode if the device is not connected with a host, otherwise reject to suspend. Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1626340078-29111-13-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3.h | 1 + drivers/usb/mtu3/mtu3_core.c | 68 ++++++++++++++++++++++++++++++++++++++++-- drivers/usb/mtu3/mtu3_dr.h | 14 +++++++++ drivers/usb/mtu3/mtu3_gadget.c | 5 ++++ drivers/usb/mtu3/mtu3_plat.c | 47 +++++++++++++++++++++-------- 5 files changed, 121 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3.h b/drivers/usb/mtu3/mtu3.h index 171e5b383063..022bbdc54e68 100644 --- a/drivers/usb/mtu3/mtu3.h +++ b/drivers/usb/mtu3/mtu3.h @@ -356,6 +356,7 @@ struct mtu3 { unsigned is_u3_ip:1; unsigned delayed_status:1; unsigned gen2cp:1; + unsigned connected:1; u8 address; u8 test_mode_nr; diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c index 648e970d77ba..a800920d38b9 100644 --- a/drivers/usb/mtu3/mtu3_core.c +++ b/drivers/usb/mtu3/mtu3_core.c @@ -9,6 +9,7 @@ */ #include +#include #include #include #include @@ -380,6 +381,24 @@ void mtu3_stop(struct mtu3 *mtu) mtu3_dev_power_down(mtu); } +static void mtu3_dev_suspend(struct mtu3 *mtu) +{ + if (!mtu->is_active) + return; + + mtu3_intr_disable(mtu); + mtu3_dev_power_down(mtu); +} + +static void mtu3_dev_resume(struct mtu3 *mtu) +{ + if (!mtu->is_active) + return; + + mtu3_dev_power_on(mtu); + mtu3_intr_enable(mtu); +} + /* for non-ep0 */ int mtu3_config_ep(struct mtu3 *mtu, struct mtu3_ep *mep, int interval, int burst, int mult) @@ -700,11 +719,15 @@ static irqreturn_t mtu3_link_isr(struct mtu3 *mtu) mtu->g.speed = udev_speed; mtu->g.ep0->maxpacket = maxpkt; mtu->ep0_state = MU3D_EP0_STATE_SETUP; + mtu->connected = !!(udev_speed != USB_SPEED_UNKNOWN); - if (udev_speed == USB_SPEED_UNKNOWN) + if (udev_speed == USB_SPEED_UNKNOWN) { mtu3_gadget_disconnect(mtu); - else + pm_runtime_put(mtu->dev); + } else { + pm_runtime_get(mtu->dev); mtu3_ep0_setup(mtu); + } return IRQ_HANDLED; } @@ -984,3 +1007,44 @@ void ssusb_gadget_exit(struct ssusb_mtk *ssusb) device_init_wakeup(ssusb->dev, false); mtu3_hw_exit(mtu); } + +int ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg) +{ + struct mtu3 *mtu = ssusb->u3d; + void __iomem *ibase = mtu->ippc_base; + u32 value; + int ret = 0; + + if (!mtu->gadget_driver) + return 0; + + if (mtu->connected) + return -EBUSY; + + mtu3_dev_suspend(mtu); + synchronize_irq(mtu->irq); + + /* wait for ip to sleep */ + if (mtu->is_active && mtu->softconnect) { + ret = readl_poll_timeout(ibase + U3D_SSUSB_IP_PW_STS1, + value, (value & SSUSB_IP_SLEEP_STS), 100, 100000); + if (ret) { + dev_err(mtu->dev, "ip sleep failed!!!\n"); + ret = -EBUSY; + } + } + + return ret; +} + +int ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg) +{ + struct mtu3 *mtu = ssusb->u3d; + + if (!mtu->gadget_driver) + return 0; + + mtu3_dev_resume(mtu); + + return 0; +} diff --git a/drivers/usb/mtu3/mtu3_dr.h b/drivers/usb/mtu3/mtu3_dr.h index 70dbf4706138..5286f9f5ee18 100644 --- a/drivers/usb/mtu3/mtu3_dr.h +++ b/drivers/usb/mtu3/mtu3_dr.h @@ -57,6 +57,8 @@ static inline void ssusb_wakeup_set(struct ssusb_mtk *ssusb, bool enable) #if IS_ENABLED(CONFIG_USB_MTU3_GADGET) || IS_ENABLED(CONFIG_USB_MTU3_DUAL_ROLE) int ssusb_gadget_init(struct ssusb_mtk *ssusb); void ssusb_gadget_exit(struct ssusb_mtk *ssusb); +int ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg); +int ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg); #else static inline int ssusb_gadget_init(struct ssusb_mtk *ssusb) { @@ -65,6 +67,18 @@ static inline int ssusb_gadget_init(struct ssusb_mtk *ssusb) static inline void ssusb_gadget_exit(struct ssusb_mtk *ssusb) {} + +static inline int +ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg) +{ + return 0; +} + +static inline int +ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg) +{ + return 0; +} #endif diff --git a/drivers/usb/mtu3/mtu3_gadget.c b/drivers/usb/mtu3/mtu3_gadget.c index 5e21ba05ebf0..7b54631ca335 100644 --- a/drivers/usb/mtu3/mtu3_gadget.c +++ b/drivers/usb/mtu3/mtu3_gadget.c @@ -469,6 +469,8 @@ static int mtu3_gadget_pullup(struct usb_gadget *gadget, int is_on) dev_dbg(mtu->dev, "%s (%s) for %sactive device\n", __func__, is_on ? "on" : "off", mtu->is_active ? "" : "in"); + pm_runtime_get_sync(mtu->dev); + /* we'd rather not pullup unless the device is active. */ spin_lock_irqsave(&mtu->lock, flags); @@ -482,6 +484,7 @@ static int mtu3_gadget_pullup(struct usb_gadget *gadget, int is_on) } spin_unlock_irqrestore(&mtu->lock, flags); + pm_runtime_put(mtu->dev); return 0; } @@ -499,6 +502,7 @@ static int mtu3_gadget_start(struct usb_gadget *gadget, } dev_dbg(mtu->dev, "bind driver %s\n", driver->function); + pm_runtime_get_sync(mtu->dev); spin_lock_irqsave(&mtu->lock, flags); @@ -509,6 +513,7 @@ static int mtu3_gadget_start(struct usb_gadget *gadget, mtu3_start(mtu); spin_unlock_irqrestore(&mtu->lock, flags); + pm_runtime_put(mtu->dev); return 0; } diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index 2be890f84c94..e174ada689f2 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -421,21 +421,32 @@ static int mtu3_remove(struct platform_device *pdev) return 0; } -/* - * when support dual-role mode, we reject suspend when - * it works as device mode; - */ static int mtu3_suspend_common(struct device *dev, pm_message_t msg) { struct ssusb_mtk *ssusb = dev_get_drvdata(dev); + int ret = 0; dev_dbg(dev, "%s\n", __func__); - /* REVISIT: disconnect it for only device mode? */ - if (!ssusb->is_host) - return 0; + switch (ssusb->dr_mode) { + case USB_DR_MODE_PERIPHERAL: + ret = ssusb_gadget_suspend(ssusb, msg); + if (ret) + return ret; - ssusb_host_suspend(ssusb); + break; + case USB_DR_MODE_HOST: + ssusb_host_suspend(ssusb); + break; + case USB_DR_MODE_OTG: + if (!ssusb->is_host) + return 0; + + ssusb_host_suspend(ssusb); + break; + default: + return -EINVAL; + } ssusb_phy_power_off(ssusb); clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks); ssusb_wakeup_set(ssusb, true); @@ -450,9 +461,6 @@ static int mtu3_resume_common(struct device *dev, pm_message_t msg) dev_dbg(dev, "%s\n", __func__); - if (!ssusb->is_host) - return 0; - ssusb_wakeup_set(ssusb, false); ret = clk_bulk_prepare_enable(BULK_CLKS_CNT, ssusb->clks); if (ret) @@ -462,7 +470,22 @@ static int mtu3_resume_common(struct device *dev, pm_message_t msg) if (ret) goto phy_err; - ssusb_host_resume(ssusb, false); + switch (ssusb->dr_mode) { + case USB_DR_MODE_PERIPHERAL: + ssusb_gadget_resume(ssusb, msg); + break; + case USB_DR_MODE_HOST: + ssusb_host_resume(ssusb, false); + break; + case USB_DR_MODE_OTG: + if (!ssusb->is_host) + return 0; + + ssusb_host_resume(ssusb, true); + break; + default: + return -EINVAL; + } return 0; -- cgit v1.2.3 From 6b587394c65c23d5ba05a33e5899e2ed8dab3c97 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Thu, 15 Jul 2021 17:07:58 +0800 Subject: usb: mtu3: support suspend/resume for dual-role mode Support suspend/resume for dual-role mode including the single port and multi-ports supported by host controller, when the host supports mult-ports, only port0 (u2/u3) is used to support dual role mode. Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1626340078-29111-14-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_core.c | 32 +++++++++-------- drivers/usb/mtu3/mtu3_dr.c | 2 ++ drivers/usb/mtu3/mtu3_dr.h | 8 +++++ drivers/usb/mtu3/mtu3_host.c | 10 +----- drivers/usb/mtu3/mtu3_plat.c | 84 ++++++++++++++++++++++++++++++++------------ 5 files changed, 89 insertions(+), 47 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c index a800920d38b9..f90e5cdec614 100644 --- a/drivers/usb/mtu3/mtu3_core.c +++ b/drivers/usb/mtu3/mtu3_core.c @@ -9,7 +9,6 @@ */ #include -#include #include #include #include @@ -1008,12 +1007,25 @@ void ssusb_gadget_exit(struct ssusb_mtk *ssusb) mtu3_hw_exit(mtu); } +bool ssusb_gadget_ip_sleep_check(struct ssusb_mtk *ssusb) +{ + struct mtu3 *mtu = ssusb->u3d; + + /* host only, should wait for ip sleep */ + if (!mtu) + return true; + + /* device is started and pullup D+, ip can sleep */ + if (mtu->is_active && mtu->softconnect) + return true; + + /* ip can't sleep if not pullup D+ when support device mode */ + return false; +} + int ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg) { struct mtu3 *mtu = ssusb->u3d; - void __iomem *ibase = mtu->ippc_base; - u32 value; - int ret = 0; if (!mtu->gadget_driver) return 0; @@ -1024,17 +1036,7 @@ int ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg) mtu3_dev_suspend(mtu); synchronize_irq(mtu->irq); - /* wait for ip to sleep */ - if (mtu->is_active && mtu->softconnect) { - ret = readl_poll_timeout(ibase + U3D_SSUSB_IP_PW_STS1, - value, (value & SSUSB_IP_SLEEP_STS), 100, 100000); - if (ret) { - dev_err(mtu->dev, "ip sleep failed!!!\n"); - ret = -EBUSY; - } - } - - return ret; + return 0; } int ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg) diff --git a/drivers/usb/mtu3/mtu3_dr.c b/drivers/usb/mtu3/mtu3_dr.c index 30e7e5fc0f88..a6b04831b20b 100644 --- a/drivers/usb/mtu3/mtu3_dr.c +++ b/drivers/usb/mtu3/mtu3_dr.c @@ -149,6 +149,7 @@ static void ssusb_mode_sw_work(struct work_struct *work) dev_dbg(ssusb->dev, "set role : %s\n", usb_role_string(desired_role)); mtu3_dbg_trace(ssusb->dev, "set role : %s", usb_role_string(desired_role)); + pm_runtime_get_sync(ssusb->dev); switch (desired_role) { case USB_ROLE_HOST: @@ -169,6 +170,7 @@ static void ssusb_mode_sw_work(struct work_struct *work) default: dev_err(ssusb->dev, "invalid role\n"); } + pm_runtime_put(ssusb->dev); } static void ssusb_set_mode(struct otg_switch_mtk *otg_sx, enum usb_role role) diff --git a/drivers/usb/mtu3/mtu3_dr.h b/drivers/usb/mtu3/mtu3_dr.h index 5286f9f5ee18..e325508bddf4 100644 --- a/drivers/usb/mtu3/mtu3_dr.h +++ b/drivers/usb/mtu3/mtu3_dr.h @@ -59,6 +59,8 @@ int ssusb_gadget_init(struct ssusb_mtk *ssusb); void ssusb_gadget_exit(struct ssusb_mtk *ssusb); int ssusb_gadget_suspend(struct ssusb_mtk *ssusb, pm_message_t msg); int ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg); +bool ssusb_gadget_ip_sleep_check(struct ssusb_mtk *ssusb); + #else static inline int ssusb_gadget_init(struct ssusb_mtk *ssusb) { @@ -79,6 +81,12 @@ ssusb_gadget_resume(struct ssusb_mtk *ssusb, pm_message_t msg) { return 0; } + +static inline bool ssusb_gadget_ip_sleep_check(struct ssusb_mtk *ssusb) +{ + return true; +} + #endif diff --git a/drivers/usb/mtu3/mtu3_host.c b/drivers/usb/mtu3/mtu3_host.c index a0a6a181b752..7d528f3c2482 100644 --- a/drivers/usb/mtu3/mtu3_host.c +++ b/drivers/usb/mtu3/mtu3_host.c @@ -8,7 +8,6 @@ */ #include -#include #include #include #include @@ -254,7 +253,6 @@ int ssusb_host_suspend(struct ssusb_mtk *ssusb) int num_u3p = ssusb->u3_ports; int num_u2p = ssusb->u2_ports; u32 value; - int ret; int i; /* power down u3 ports except skipped ones */ @@ -280,13 +278,7 @@ int ssusb_host_suspend(struct ssusb_mtk *ssusb) /* power down host ip */ mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN); - /* wait for host ip to sleep */ - ret = readl_poll_timeout(ibase + U3D_SSUSB_IP_PW_STS1, value, - (value & SSUSB_IP_SLEEP_STS), 100, 100000); - if (ret) - dev_err(ssusb->dev, "ip sleep failed!!!\n"); - - return ret; + return 0; } static void ssusb_host_setup(struct ssusb_mtk *ssusb) diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index e174ada689f2..81266f34986f 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -45,6 +45,29 @@ int ssusb_check_clocks(struct ssusb_mtk *ssusb, u32 ex_clks) return 0; } +static int wait_for_ip_sleep(struct ssusb_mtk *ssusb) +{ + bool sleep_check = true; + u32 value; + int ret; + + if (!ssusb->is_host) + sleep_check = ssusb_gadget_ip_sleep_check(ssusb); + + if (!sleep_check) + return 0; + + /* wait for ip enter sleep mode */ + ret = readl_poll_timeout(ssusb->ippc_base + U3D_SSUSB_IP_PW_STS1, value, + (value & SSUSB_IP_SLEEP_STS), 100, 100000); + if (ret) { + dev_err(ssusb->dev, "ip sleep failed!!!\n"); + ret = -EBUSY; + } + + return ret; +} + static int ssusb_phy_init(struct ssusb_mtk *ssusb) { int i; @@ -421,6 +444,28 @@ static int mtu3_remove(struct platform_device *pdev) return 0; } +static int resume_ip_and_ports(struct ssusb_mtk *ssusb, pm_message_t msg) +{ + switch (ssusb->dr_mode) { + case USB_DR_MODE_PERIPHERAL: + ssusb_gadget_resume(ssusb, msg); + break; + case USB_DR_MODE_HOST: + ssusb_host_resume(ssusb, false); + break; + case USB_DR_MODE_OTG: + ssusb_host_resume(ssusb, !ssusb->is_host); + if (!ssusb->is_host) + ssusb_gadget_resume(ssusb, msg); + + break; + default: + return -EINVAL; + } + + return 0; +} + static int mtu3_suspend_common(struct device *dev, pm_message_t msg) { struct ssusb_mtk *ssusb = dev_get_drvdata(dev); @@ -432,26 +477,36 @@ static int mtu3_suspend_common(struct device *dev, pm_message_t msg) case USB_DR_MODE_PERIPHERAL: ret = ssusb_gadget_suspend(ssusb, msg); if (ret) - return ret; + goto err; break; case USB_DR_MODE_HOST: ssusb_host_suspend(ssusb); break; case USB_DR_MODE_OTG: - if (!ssusb->is_host) - return 0; - + if (!ssusb->is_host) { + ret = ssusb_gadget_suspend(ssusb, msg); + if (ret) + goto err; + } ssusb_host_suspend(ssusb); break; default: return -EINVAL; } + + ret = wait_for_ip_sleep(ssusb); + if (ret) + goto sleep_err; + ssusb_phy_power_off(ssusb); clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks); ssusb_wakeup_set(ssusb, true); - return 0; +sleep_err: + resume_ip_and_ports(ssusb, msg); +err: + return ret; } static int mtu3_resume_common(struct device *dev, pm_message_t msg) @@ -470,24 +525,7 @@ static int mtu3_resume_common(struct device *dev, pm_message_t msg) if (ret) goto phy_err; - switch (ssusb->dr_mode) { - case USB_DR_MODE_PERIPHERAL: - ssusb_gadget_resume(ssusb, msg); - break; - case USB_DR_MODE_HOST: - ssusb_host_resume(ssusb, false); - break; - case USB_DR_MODE_OTG: - if (!ssusb->is_host) - return 0; - - ssusb_host_resume(ssusb, true); - break; - default: - return -EINVAL; - } - - return 0; + return resume_ip_and_ports(ssusb, msg); phy_err: clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks); -- cgit v1.2.3 From c6e23b89a95db73bb85be28079664c66389323f0 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Mon, 28 Jun 2021 17:53:07 +0200 Subject: usb: dwc3: gadget: set gadgets parent to the right controller In case of dwc3 it is possible that the sysdev is the parent of the controller. To ensure the right dev is set to the gadget's dev parent we will hand over sysdev instead of dev, which will always point to the controller. Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20210628155311.16762-2-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index fb5a09ffebb0..a29a4ca833c1 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -4233,7 +4233,7 @@ int dwc3_gadget_init(struct dwc3 *dwc) } - usb_initialize_gadget(dwc->dev, dwc->gadget, dwc_gadget_release); + usb_initialize_gadget(dwc->sysdev, dwc->gadget, dwc_gadget_release); dev = &dwc->gadget->dev; dev->platform_data = dwc; dwc->gadget->ops = &dwc3_gadget_ops; -- cgit v1.2.3 From 9973772dbb2b9c12a0707eca692f0dabf6295978 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Mon, 28 Jun 2021 17:53:08 +0200 Subject: usb: gadget: uvc: make uvc_num_requests depend on gadget speed While sending bigger images is possible with USB_SPEED_SUPER it is better to use more isochronous requests in flight. This patch makes the number uvc_num_requests dynamic by changing it depending on the gadget speed. Reviewed-by: Paul Elder Reviewed-by: Laurent Pinchart Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20210628155311.16762-3-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc.h | 11 +++++-- drivers/usb/gadget/function/uvc_queue.c | 6 ++++ drivers/usb/gadget/function/uvc_video.c | 55 ++++++++++++++++++++------------- 3 files changed, 47 insertions(+), 25 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h index 23ee25383c1f..52f894183ecf 100644 --- a/drivers/usb/gadget/function/uvc.h +++ b/drivers/usb/gadget/function/uvc.h @@ -65,13 +65,17 @@ extern unsigned int uvc_gadget_trace_param; * Driver specific constants */ -#define UVC_NUM_REQUESTS 4 #define UVC_MAX_REQUEST_SIZE 64 #define UVC_MAX_EVENTS 4 /* ------------------------------------------------------------------------ * Structures */ +struct uvc_request { + struct usb_request *req; + u8 *req_buffer; + struct uvc_video *video; +}; struct uvc_video { struct uvc_device *uvc; @@ -87,10 +91,11 @@ struct uvc_video { unsigned int imagesize; struct mutex mutex; /* protects frame parameters */ + unsigned int uvc_num_requests; + /* Requests */ unsigned int req_size; - struct usb_request *req[UVC_NUM_REQUESTS]; - __u8 *req_buffer[UVC_NUM_REQUESTS]; + struct uvc_request *ureq; struct list_head req_free; spinlock_t req_lock; diff --git a/drivers/usb/gadget/function/uvc_queue.c b/drivers/usb/gadget/function/uvc_queue.c index 61e2c94cc0b0..ff0cc08531d2 100644 --- a/drivers/usb/gadget/function/uvc_queue.c +++ b/drivers/usb/gadget/function/uvc_queue.c @@ -43,6 +43,7 @@ static int uvc_queue_setup(struct vb2_queue *vq, { struct uvc_video_queue *queue = vb2_get_drv_priv(vq); struct uvc_video *video = container_of(queue, struct uvc_video, queue); + struct usb_composite_dev *cdev = video->uvc->func.config->cdev; if (*nbuffers > UVC_MAX_VIDEO_BUFFERS) *nbuffers = UVC_MAX_VIDEO_BUFFERS; @@ -51,6 +52,11 @@ static int uvc_queue_setup(struct vb2_queue *vq, sizes[0] = video->imagesize; + if (cdev->gadget->speed < USB_SPEED_SUPER) + video->uvc_num_requests = 4; + else + video->uvc_num_requests = 64; + return 0; } diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index 633e23d58d86..303cb427f687 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -145,7 +145,8 @@ static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req) static void uvc_video_complete(struct usb_ep *ep, struct usb_request *req) { - struct uvc_video *video = req->context; + struct uvc_request *ureq = req->context; + struct uvc_video *video = ureq->video; struct uvc_video_queue *queue = &video->queue; unsigned long flags; @@ -177,16 +178,21 @@ uvc_video_free_requests(struct uvc_video *video) { unsigned int i; - for (i = 0; i < UVC_NUM_REQUESTS; ++i) { - if (video->req[i]) { - usb_ep_free_request(video->ep, video->req[i]); - video->req[i] = NULL; + if (video->ureq) { + for (i = 0; i < video->uvc_num_requests; ++i) { + if (video->ureq[i].req) { + usb_ep_free_request(video->ep, video->ureq[i].req); + video->ureq[i].req = NULL; + } + + if (video->ureq[i].req_buffer) { + kfree(video->ureq[i].req_buffer); + video->ureq[i].req_buffer = NULL; + } } - if (video->req_buffer[i]) { - kfree(video->req_buffer[i]); - video->req_buffer[i] = NULL; - } + kfree(video->ureq); + video->ureq = NULL; } INIT_LIST_HEAD(&video->req_free); @@ -207,21 +213,26 @@ uvc_video_alloc_requests(struct uvc_video *video) * max_t(unsigned int, video->ep->maxburst, 1) * (video->ep->mult); - for (i = 0; i < UVC_NUM_REQUESTS; ++i) { - video->req_buffer[i] = kmalloc(req_size, GFP_KERNEL); - if (video->req_buffer[i] == NULL) + video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL); + if (video->ureq == NULL) + return -ENOMEM; + + for (i = 0; i < video->uvc_num_requests; ++i) { + video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL); + if (video->ureq[i].req_buffer == NULL) goto error; - video->req[i] = usb_ep_alloc_request(video->ep, GFP_KERNEL); - if (video->req[i] == NULL) + video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL); + if (video->ureq[i].req == NULL) goto error; - video->req[i]->buf = video->req_buffer[i]; - video->req[i]->length = 0; - video->req[i]->complete = uvc_video_complete; - video->req[i]->context = video; + video->ureq[i].req->buf = video->ureq[i].req_buffer; + video->ureq[i].req->length = 0; + video->ureq[i].req->complete = uvc_video_complete; + video->ureq[i].req->context = &video->ureq[i]; + video->ureq[i].video = video; - list_add_tail(&video->req[i]->list, &video->req_free); + list_add_tail(&video->ureq[i].req->list, &video->req_free); } video->req_size = req_size; @@ -312,9 +323,9 @@ int uvcg_video_enable(struct uvc_video *video, int enable) cancel_work_sync(&video->pump); uvcg_queue_cancel(&video->queue, 0); - for (i = 0; i < UVC_NUM_REQUESTS; ++i) - if (video->req[i]) - usb_ep_dequeue(video->ep, video->req[i]); + for (i = 0; i < video->uvc_num_requests; ++i) + if (video->ureq && video->ureq[i].req) + usb_ep_dequeue(video->ep, video->ureq[i].req); uvc_video_free_requests(video); uvcg_queue_enable(&video->queue, 0); -- cgit v1.2.3 From b9b82d3d0dbc45ee6b4817c7a7275a65152301f5 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Mon, 28 Jun 2021 17:53:09 +0200 Subject: usb: gadget: uvc: set v4l2_dev->dev in f_uvc The v4l2_dev has no corresponding device to it. We will point it to the gadget's dev. Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20210628155311.16762-4-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_uvc.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index f48a00e49794..9d87c0fb8f92 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -418,6 +418,7 @@ uvc_register_video(struct uvc_device *uvc) /* TODO reference counting. */ uvc->vdev.v4l2_dev = &uvc->v4l2_dev; + uvc->vdev.v4l2_dev->dev = &cdev->gadget->dev; uvc->vdev.fops = &uvc_v4l2_fops; uvc->vdev.ioctl_ops = &uvc_v4l2_ioctl_ops; uvc->vdev.release = video_device_release_empty; -- cgit v1.2.3 From e81e7f9a0eb9536d5976acf5d95290338032a198 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Mon, 28 Jun 2021 17:53:10 +0200 Subject: usb: gadget: uvc: add scatter gather support This patch adds support for scatter gather transfers. If the underlying gadgets sg_supported == true, then the videeobuf2-dma-sg is used and the encode routine maps all scatter entries to separate scatterlists for the usb gadget. When streaming 1080p with request size of 1024 times 3 bytes top shows a difference of about 6.4% CPU load applying this patch: PID USER PR NI VIRT RES %CPU %MEM TIME+ S COMMAND 64 root 0 -20 0.0m 0.0m 7.7 0.0 0:01.25 I [kworker/u5:0-uvcvideo] 83 root 0 -20 0.0m 0.0m 4.5 0.0 0:03.71 I [kworker/u5:3-uvcvideo] 307 root -51 0 0.0m 0.0m 3.8 0.0 0:01.05 S [irq/51-dwc3] vs. 64 root 0 -20 0.0m 0.0m 5.8 0.0 0:01.79 I [kworker/u5:0-uvcvideo] 306 root -51 0 0.0m 0.0m 3.2 0.0 0:01.97 S [irq/51-dwc3] 82 root 0 -20 0.0m 0.0m 0.6 0.0 0:01.86 I [kworker/u5:1-uvcvideo] Signed-off-by: Michael Grzeschik Link: https://lore.kernel.org/r/20210628155311.16762-5-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/Kconfig | 1 + drivers/usb/gadget/function/uvc.h | 2 + drivers/usb/gadget/function/uvc_queue.c | 22 +++++++-- drivers/usb/gadget/function/uvc_queue.h | 7 ++- drivers/usb/gadget/function/uvc_video.c | 82 +++++++++++++++++++++++++++++++-- drivers/usb/gadget/function/uvc_video.h | 2 + drivers/usb/gadget/legacy/Kconfig | 1 + 7 files changed, 108 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 2d152571a7de..dd58094f0b85 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -450,6 +450,7 @@ config USB_CONFIGFS_F_UVC depends on USB_CONFIGFS depends on VIDEO_V4L2 depends on VIDEO_DEV + select VIDEOBUF2_DMA_SG select VIDEOBUF2_VMALLOC select USB_F_UVC help diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h index 52f894183ecf..36b78f8b3cd4 100644 --- a/drivers/usb/gadget/function/uvc.h +++ b/drivers/usb/gadget/function/uvc.h @@ -75,6 +75,8 @@ struct uvc_request { struct usb_request *req; u8 *req_buffer; struct uvc_video *video; + struct sg_table sgt; + u8 header[2]; }; struct uvc_video { diff --git a/drivers/usb/gadget/function/uvc_queue.c b/drivers/usb/gadget/function/uvc_queue.c index ff0cc08531d2..7d00ad7c154c 100644 --- a/drivers/usb/gadget/function/uvc_queue.c +++ b/drivers/usb/gadget/function/uvc_queue.c @@ -17,6 +17,7 @@ #include #include +#include #include #include "uvc.h" @@ -76,7 +77,12 @@ static int uvc_buffer_prepare(struct vb2_buffer *vb) return -ENODEV; buf->state = UVC_BUF_STATE_QUEUED; - buf->mem = vb2_plane_vaddr(vb, 0); + if (queue->use_sg) { + buf->sgt = vb2_dma_sg_plane_desc(vb, 0); + buf->sg = buf->sgt->sgl; + } else { + buf->mem = vb2_plane_vaddr(vb, 0); + } buf->length = vb2_plane_size(vb, 0); if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) buf->bytesused = 0; @@ -116,9 +122,11 @@ static const struct vb2_ops uvc_queue_qops = { .wait_finish = vb2_ops_wait_finish, }; -int uvcg_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type, +int uvcg_queue_init(struct uvc_video_queue *queue, struct device *dev, enum v4l2_buf_type type, struct mutex *lock) { + struct uvc_video *video = container_of(queue, struct uvc_video, queue); + struct usb_composite_dev *cdev = video->uvc->func.config->cdev; int ret; queue->queue.type = type; @@ -127,9 +135,17 @@ int uvcg_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type, queue->queue.buf_struct_size = sizeof(struct uvc_buffer); queue->queue.ops = &uvc_queue_qops; queue->queue.lock = lock; - queue->queue.mem_ops = &vb2_vmalloc_memops; + if (cdev->gadget->sg_supported) { + queue->queue.mem_ops = &vb2_dma_sg_memops; + queue->use_sg = 1; + } else { + queue->queue.mem_ops = &vb2_vmalloc_memops; + } + queue->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC | V4L2_BUF_FLAG_TSTAMP_SRC_EOF; + queue->queue.dev = dev; + ret = vb2_queue_init(&queue->queue); if (ret) return ret; diff --git a/drivers/usb/gadget/function/uvc_queue.h b/drivers/usb/gadget/function/uvc_queue.h index 2f0fff769843..05360a0767f6 100644 --- a/drivers/usb/gadget/function/uvc_queue.h +++ b/drivers/usb/gadget/function/uvc_queue.h @@ -34,6 +34,9 @@ struct uvc_buffer { enum uvc_buffer_state state; void *mem; + struct sg_table *sgt; + struct scatterlist *sg; + unsigned int offset; unsigned int length; unsigned int bytesused; }; @@ -50,6 +53,8 @@ struct uvc_video_queue { unsigned int buf_used; + bool use_sg; + spinlock_t irqlock; /* Protects flags and irqqueue */ struct list_head irqqueue; }; @@ -59,7 +64,7 @@ static inline int uvc_queue_streaming(struct uvc_video_queue *queue) return vb2_is_streaming(&queue->queue); } -int uvcg_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type, +int uvcg_queue_init(struct uvc_video_queue *queue, struct device *dev, enum v4l2_buf_type type, struct mutex *lock); void uvcg_free_buffers(struct uvc_video_queue *queue); diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index 303cb427f687..2cefb8bd4f15 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -27,10 +27,10 @@ static int uvc_video_encode_header(struct uvc_video *video, struct uvc_buffer *buf, u8 *data, int len) { - data[0] = 2; + data[0] = UVCG_REQUEST_HEADER_LEN; data[1] = UVC_STREAM_EOH | video->fid; - if (buf->bytesused - video->queue.buf_used <= len - 2) + if (buf->bytesused - video->queue.buf_used <= len - UVCG_REQUEST_HEADER_LEN) data[1] |= UVC_STREAM_EOF; return 2; @@ -94,6 +94,71 @@ uvc_video_encode_bulk(struct usb_request *req, struct uvc_video *video, video->payload_size = 0; } +static void +uvc_video_encode_isoc_sg(struct usb_request *req, struct uvc_video *video, + struct uvc_buffer *buf) +{ + unsigned int pending = buf->bytesused - video->queue.buf_used; + struct uvc_request *ureq = req->context; + struct scatterlist *sg, *iter; + unsigned int len = video->req_size; + unsigned int sg_left, part = 0; + unsigned int i; + int ret; + + sg = ureq->sgt.sgl; + sg_init_table(sg, ureq->sgt.nents); + + /* Init the header. */ + ret = uvc_video_encode_header(video, buf, ureq->header, + video->req_size); + sg_set_buf(sg, ureq->header, UVCG_REQUEST_HEADER_LEN); + len -= ret; + + if (pending <= len) + len = pending; + + req->length = (len == pending) ? + len + UVCG_REQUEST_HEADER_LEN : video->req_size; + + /* Init the pending sgs with payload */ + sg = sg_next(sg); + + for_each_sg(sg, iter, ureq->sgt.nents - 1, i) { + if (!len || !buf->sg) + break; + + sg_left = sg_dma_len(buf->sg) - buf->offset; + part = min_t(unsigned int, len, sg_left); + + sg_set_page(iter, sg_page(buf->sg), part, buf->offset); + + if (part == sg_left) { + buf->offset = 0; + buf->sg = sg_next(buf->sg); + } else { + buf->offset += part; + } + len -= part; + } + + /* Assign the video data with header. */ + req->buf = NULL; + req->sg = ureq->sgt.sgl; + req->num_sgs = i + 1; + + req->length -= len; + video->queue.buf_used += req->length - UVCG_REQUEST_HEADER_LEN; + + if (buf->bytesused == video->queue.buf_used || !buf->sg) { + video->queue.buf_used = 0; + buf->state = UVC_BUF_STATE_DONE; + buf->offset = 0; + uvcg_queue_next_buffer(&video->queue, buf); + video->fid ^= UVC_STREAM_FID; + } +} + static void uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video, struct uvc_buffer *buf) @@ -180,6 +245,8 @@ uvc_video_free_requests(struct uvc_video *video) if (video->ureq) { for (i = 0; i < video->uvc_num_requests; ++i) { + sg_free_table(&video->ureq[i].sgt); + if (video->ureq[i].req) { usb_ep_free_request(video->ep, video->ureq[i].req); video->ureq[i].req = NULL; @@ -233,6 +300,10 @@ uvc_video_alloc_requests(struct uvc_video *video) video->ureq[i].video = video; list_add_tail(&video->ureq[i].req->list, &video->req_free); + /* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */ + sg_alloc_table(&video->ureq[i].sgt, + DIV_ROUND_UP(req_size - 2, PAGE_SIZE) + 2, + GFP_KERNEL); } video->req_size = req_size; @@ -342,7 +413,8 @@ int uvcg_video_enable(struct uvc_video *video, int enable) video->encode = uvc_video_encode_bulk; video->payload_size = 0; } else - video->encode = uvc_video_encode_isoc; + video->encode = video->queue.use_sg ? + uvc_video_encode_isoc_sg : uvc_video_encode_isoc; schedule_work(&video->pump); @@ -366,8 +438,8 @@ int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc) video->imagesize = 320 * 240 * 2; /* Initialize the video buffers queue. */ - uvcg_queue_init(&video->queue, V4L2_BUF_TYPE_VIDEO_OUTPUT, - &video->mutex); + uvcg_queue_init(&video->queue, uvc->v4l2_dev.dev->parent, + V4L2_BUF_TYPE_VIDEO_OUTPUT, &video->mutex); return 0; } diff --git a/drivers/usb/gadget/function/uvc_video.h b/drivers/usb/gadget/function/uvc_video.h index 03adeefa343b..9bf19475f6f9 100644 --- a/drivers/usb/gadget/function/uvc_video.h +++ b/drivers/usb/gadget/function/uvc_video.h @@ -12,6 +12,8 @@ #ifndef __UVC_VIDEO_H__ #define __UVC_VIDEO_H__ +#define UVCG_REQUEST_HEADER_LEN 2 + struct uvc_video; int uvcg_video_enable(struct uvc_video *video, int enable); diff --git a/drivers/usb/gadget/legacy/Kconfig b/drivers/usb/gadget/legacy/Kconfig index 11dd6e8adc8a..de6668e58481 100644 --- a/drivers/usb/gadget/legacy/Kconfig +++ b/drivers/usb/gadget/legacy/Kconfig @@ -502,6 +502,7 @@ config USB_G_WEBCAM tristate "USB Webcam Gadget" depends on VIDEO_V4L2 select USB_LIBCOMPOSITE + select VIDEOBUF2_DMA_SG select VIDEOBUF2_VMALLOC select USB_F_UVC help -- cgit v1.2.3 From fc78941d8169cba40e420eb24a88789b0406c738 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Mon, 28 Jun 2021 17:53:11 +0200 Subject: usb: gadget: uvc: decrease the interrupt load to a quarter With usb3 we handle many more requests. Decrease the interrupt load by only enabling the interrupt every quarter of the allocated requests. Reviewed-by: Paul Elder Signed-off-by: Michael Grzeschik -- v1 -> v2: - edited patch description - removed extra parantheses - added a comment for the logic - using unsigned int instead of int - reinitializing req_int_count in uvcg_video_enable v2 -> v3: - Link: https://lore.kernel.org/r/20210628155311.16762-6-m.grzeschik@pengutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/uvc.h | 2 ++ drivers/usb/gadget/function/uvc_video.c | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h index 36b78f8b3cd4..255a61bd6a6a 100644 --- a/drivers/usb/gadget/function/uvc.h +++ b/drivers/usb/gadget/function/uvc.h @@ -101,6 +101,8 @@ struct uvc_video { struct list_head req_free; spinlock_t req_lock; + unsigned int req_int_count; + void (*encode) (struct usb_request *req, struct uvc_video *video, struct uvc_buffer *buf); diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index 2cefb8bd4f15..b4a763e5f70e 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -360,6 +360,19 @@ static void uvcg_video_pump(struct work_struct *work) video->encode(req, video, buf); + /* With usb3 we have more requests. This will decrease the + * interrupt load to a quarter but also catches the corner + * cases, which needs to be handled */ + if (list_empty(&video->req_free) || + buf->state == UVC_BUF_STATE_DONE || + !(video->req_int_count % + DIV_ROUND_UP(video->uvc_num_requests, 4))) { + video->req_int_count = 0; + req->no_interrupt = 0; + } else { + req->no_interrupt = 1; + } + /* Queue the USB request */ ret = uvcg_video_ep_queue(video, req); spin_unlock_irqrestore(&queue->irqlock, flags); @@ -368,6 +381,7 @@ static void uvcg_video_pump(struct work_struct *work) uvcg_queue_cancel(queue, 0); break; } + video->req_int_count++; } spin_lock_irqsave(&video->req_lock, flags); @@ -416,6 +430,8 @@ int uvcg_video_enable(struct uvc_video *video, int enable) video->encode = video->queue.use_sg ? uvc_video_encode_isoc_sg : uvc_video_encode_isoc; + video->req_int_count = 0; + schedule_work(&video->pump); return ret; -- cgit v1.2.3 From 7de14c88272c05d86fce83a5cead36832ce3a424 Mon Sep 17 00:00:00 2001 From: Rui Miguel Silva Date: Tue, 27 Jul 2021 11:05:14 +0100 Subject: usb: isp1760: remove debug message as error Remove debug message leftover from the boot error buffer. Signed-off-by: Rui Miguel Silva Link: https://lore.kernel.org/r/20210727100516.4190681-2-rui.silva@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/isp1760/isp1760-hcd.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c index 27168b4a4ef2..a745c4c2b773 100644 --- a/drivers/usb/isp1760/isp1760-hcd.c +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -733,7 +733,6 @@ static int isp1760_hc_setup(struct usb_hcd *hcd) /* Change bus pattern */ scratch = isp1760_hcd_read(hcd, HC_CHIP_ID_HIGH); - dev_err(hcd->self.controller, "Scratch test 0x%08x\n", scratch); scratch = isp1760_hcd_read(hcd, HC_SCRATCH); if (scratch != pattern) { dev_err(hcd->self.controller, "Scratch test failed. 0x%08x\n", scratch); -- cgit v1.2.3 From 41f673183862a183d4ea0522c045fabfbd1b28c8 Mon Sep 17 00:00:00 2001 From: Rui Miguel Silva Date: Tue, 27 Jul 2021 11:05:15 +0100 Subject: usb: isp1760: do not sleep in field register poll When polling for a setup or clear of a register field we were sleeping in atomic context but using a very tight sleep interval. Since the use cases for this poll mechanism are only in setup and stop paths, and in practice this poll is not used most of the times but needs to be there to comply to hardware setup times, remove the sleep time and make the poll loop tighter. Reported-by: Dan Carpenter Signed-off-by: Rui Miguel Silva Link: https://lore.kernel.org/r/20210727100516.4190681-3-rui.silva@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/isp1760/isp1760-hcd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c index a745c4c2b773..a018394d54f8 100644 --- a/drivers/usb/isp1760/isp1760-hcd.c +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -250,7 +250,7 @@ static int isp1760_hcd_set_and_wait(struct usb_hcd *hcd, u32 field, isp1760_hcd_set(hcd, field); return regmap_field_read_poll_timeout(priv->fields[field], val, - val, 10, timeout_us); + val, 0, timeout_us); } static int isp1760_hcd_set_and_wait_swap(struct usb_hcd *hcd, u32 field, @@ -262,7 +262,7 @@ static int isp1760_hcd_set_and_wait_swap(struct usb_hcd *hcd, u32 field, isp1760_hcd_set(hcd, field); return regmap_field_read_poll_timeout(priv->fields[field], val, - !val, 10, timeout_us); + !val, 0, timeout_us); } static int isp1760_hcd_clear_and_wait(struct usb_hcd *hcd, u32 field, @@ -274,7 +274,7 @@ static int isp1760_hcd_clear_and_wait(struct usb_hcd *hcd, u32 field, isp1760_hcd_clear(hcd, field); return regmap_field_read_poll_timeout(priv->fields[field], val, - !val, 10, timeout_us); + !val, 0, timeout_us); } static bool isp1760_hcd_is_set(struct usb_hcd *hcd, u32 field) -- cgit v1.2.3 From cbbdb3fe0d974d655c87c3e6ba2990d5496b9f82 Mon Sep 17 00:00:00 2001 From: Rui Miguel Silva Date: Tue, 27 Jul 2021 11:05:16 +0100 Subject: usb: isp1760: rework cache initialization error handling If we fail to create qtd cache we were not destroying the urb_listitem, rework the error handling logic to cope with that. Signed-off-by: Rui Miguel Silva Link: https://lore.kernel.org/r/20210727100516.4190681-4-rui.silva@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/isp1760/isp1760-hcd.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c index a018394d54f8..825be736be33 100644 --- a/drivers/usb/isp1760/isp1760-hcd.c +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -2527,17 +2527,23 @@ int __init isp1760_init_kmem_once(void) SLAB_MEM_SPREAD, NULL); if (!qtd_cachep) - return -ENOMEM; + goto destroy_urb_listitem; qh_cachep = kmem_cache_create("isp1760_qh", sizeof(struct isp1760_qh), 0, SLAB_TEMPORARY | SLAB_MEM_SPREAD, NULL); - if (!qh_cachep) { - kmem_cache_destroy(qtd_cachep); - return -ENOMEM; - } + if (!qh_cachep) + goto destroy_qtd; return 0; + +destroy_qtd: + kmem_cache_destroy(qtd_cachep); + +destroy_urb_listitem: + kmem_cache_destroy(urb_listitem_cachep); + + return -ENOMEM; } void isp1760_deinit_kmem_cache(void) -- cgit v1.2.3 From 0132bf6f395837fc77fb38ac3d2806d22426be51 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Thu, 29 Jul 2021 00:19:21 +0200 Subject: drivers: usb: dwc3-qcom: Add sdm660 compatible Add a new compatible for SDM660's DWC3. Acked-by: Felipe Balbi Signed-off-by: Konrad Dybcio Link: https://lore.kernel.org/r/20210728221921.52068-1-konrad.dybcio@somainline.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/qcom,dwc3.yaml | 1 + drivers/usb/dwc3/dwc3-qcom.c | 1 + 2 files changed, 2 insertions(+) (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml index 4e6451789806..e70afc40edb2 100644 --- a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml @@ -17,6 +17,7 @@ properties: - qcom,msm8998-dwc3 - qcom,sc7180-dwc3 - qcom,sc7280-dwc3 + - qcom,sdm660-dwc3 - qcom,sdm845-dwc3 - qcom,sdx55-dwc3 - qcom,sm4250-dwc3 diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 25cd123e441a..1c440732db22 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -931,6 +931,7 @@ static const struct of_device_id dwc3_qcom_of_match[] = { { .compatible = "qcom,dwc3" }, { .compatible = "qcom,msm8996-dwc3" }, { .compatible = "qcom,msm8998-dwc3" }, + { .compatible = "qcom,sdm660-dwc3" }, { .compatible = "qcom,sdm845-dwc3" }, { } }; -- cgit v1.2.3 From 64cd4271ea8e6247f7ed14c443e41d0f5866aac0 Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Wed, 28 Jul 2021 11:20:52 +0200 Subject: usb: gadget: pxa25x_udc: Constify static struct pxa25x_ep_ops The struct pxa25x_ep_ops is only assigned to the ops field in the usb_ep struct, which is a pointer to const struct usb_ep_ops. Make it const to allow the compiler to put it in read-only memory. Acked-by: Felipe Balbi Signed-off-by: Rikard Falkeborn Link: https://lore.kernel.org/r/20210728092052.4178-1-rikard.falkeborn@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/pxa25x_udc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/pxa25x_udc.c b/drivers/usb/gadget/udc/pxa25x_udc.c index 69ef1e669d0c..a09ec1d826b2 100644 --- a/drivers/usb/gadget/udc/pxa25x_udc.c +++ b/drivers/usb/gadget/udc/pxa25x_udc.c @@ -1093,7 +1093,7 @@ static void pxa25x_ep_fifo_flush(struct usb_ep *_ep) } -static struct usb_ep_ops pxa25x_ep_ops = { +static const struct usb_ep_ops pxa25x_ep_ops = { .enable = pxa25x_ep_enable, .disable = pxa25x_ep_disable, -- cgit v1.2.3 From 1651d9e7810e79500b4940122e192b8aaeb2d63c Mon Sep 17 00:00:00 2001 From: Rajat Jain Date: Fri, 30 Jul 2021 16:53:04 -0700 Subject: thunderbolt: Add authorized value to the KOBJ_CHANGE uevent For security reasons, we would like to monitor and track when the Thunderbolt devices are authorized and deauthorized (i.e. when the Thunderbolt sysfs "authorized" attribute changes). Currently the userspace gets a udev change notification when there is a change, but the state may have changed (again) by the time we look at the authorized attribute in sysfs. So an authorization event may go unnoticed. Thus make it easier by informing the actual change (new value of authorized attribute) in the udev change notification. The change is included as a key value "authorized=" where is the new value of sysfs attribute "authorized", and is described at Documentation/ABI/testing/sysfs-bus-thunderbolt under /sys/bus/thunderbolt/devices/.../authorized. Signed-off-by: Rajat Jain Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 83b1ef3d5d03..bc91887f24c3 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -1498,6 +1498,7 @@ static ssize_t authorized_show(struct device *dev, static int disapprove_switch(struct device *dev, void *not_used) { + char *envp[] = { "AUTHORIZED=0", NULL }; struct tb_switch *sw; sw = tb_to_switch(dev); @@ -1514,7 +1515,7 @@ static int disapprove_switch(struct device *dev, void *not_used) return ret; sw->authorized = 0; - kobject_uevent(&sw->dev.kobj, KOBJ_CHANGE); + kobject_uevent_env(&sw->dev.kobj, KOBJ_CHANGE, envp); } return 0; @@ -1522,7 +1523,9 @@ static int disapprove_switch(struct device *dev, void *not_used) static int tb_switch_set_authorized(struct tb_switch *sw, unsigned int val) { + char envp_string[13]; int ret = -EINVAL; + char *envp[] = { envp_string, NULL }; if (!mutex_trylock(&sw->tb->lock)) return restart_syscall(); @@ -1559,8 +1562,12 @@ static int tb_switch_set_authorized(struct tb_switch *sw, unsigned int val) if (!ret) { sw->authorized = val; - /* Notify status change to the userspace */ - kobject_uevent(&sw->dev.kobj, KOBJ_CHANGE); + /* + * Notify status change to the userspace, informing the new + * value of /sys/bus/thunderbolt/devices/.../authorized. + */ + sprintf(envp_string, "AUTHORIZED=%u", sw->authorized); + kobject_uevent_env(&sw->dev.kobj, KOBJ_CHANGE, envp); } unlock: -- cgit v1.2.3 From 9311a531064be7e136e13df672959c635463be9e Mon Sep 17 00:00:00 2001 From: Wei Ming Chen Date: Sun, 1 Aug 2021 13:54:54 +0800 Subject: usb: gadget: Fix inconsistent indent Remove whitespace and use tab as indent Acked-by: Felipe Balbi Signed-off-by: Wei Ming Chen Link: https://lore.kernel.org/r/20210801055454.53015-1-jj251510319013@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/configfs.c | 8 ++++---- drivers/usb/gadget/function/f_fs.c | 2 +- drivers/usb/gadget/legacy/inode.c | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index f4c7c8241fe2..477e72a1d11e 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -55,7 +55,7 @@ struct gadget_info { static inline struct gadget_info *to_gadget_info(struct config_item *item) { - return container_of(to_config_group(item), struct gadget_info, group); + return container_of(to_config_group(item), struct gadget_info, group); } struct config_usb_cfg { @@ -365,21 +365,21 @@ static struct configfs_attribute *gadget_root_attrs[] = { static inline struct gadget_strings *to_gadget_strings(struct config_item *item) { - return container_of(to_config_group(item), struct gadget_strings, + return container_of(to_config_group(item), struct gadget_strings, group); } static inline struct gadget_config_name *to_gadget_config_name( struct config_item *item) { - return container_of(to_config_group(item), struct gadget_config_name, + return container_of(to_config_group(item), struct gadget_config_name, group); } static inline struct usb_function_instance *to_usb_function_instance( struct config_item *item) { - return container_of(to_config_group(item), + return container_of(to_config_group(item), struct usb_function_instance, group); } diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 9c0c393abb39..cc9a11b7fec9 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -2081,7 +2081,7 @@ static int __must_check ffs_do_single_desc(char *data, unsigned len, break; case USB_TYPE_CLASS | 0x01: - if (*current_class == USB_INTERFACE_CLASS_HID) { + if (*current_class == USB_INTERFACE_CLASS_HID) { pr_vdebug("hid descriptor\n"); if (length != sizeof(struct hid_descriptor)) goto inv_length; diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index cd8e2737947b..539220d7f5b6 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -1214,8 +1214,8 @@ dev_release (struct inode *inode, struct file *fd) static __poll_t ep0_poll (struct file *fd, poll_table *wait) { - struct dev_data *dev = fd->private_data; - __poll_t mask = 0; + struct dev_data *dev = fd->private_data; + __poll_t mask = 0; if (dev->state <= STATE_DEV_OPENED) return DEFAULT_POLLMASK; -- cgit v1.2.3 From 90059e9395cae00d79ecb1f7e1e702756416bc6a Mon Sep 17 00:00:00 2001 From: Salah Triki Date: Sat, 31 Jul 2021 18:18:38 +0100 Subject: usb: gadget: remove useless cast Remove the cast done by ERR_PTR() and PTR_ERR() since data is of type char * and fss_prepare_buffer() should returns a value of this type. Acked-by: Felipe Balbi Signed-off-by: Salah Triki Link: https://lore.kernel.org/r/20210731171838.GA912463@pc Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_fs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index cc9a11b7fec9..8260f38025b7 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -3831,7 +3831,7 @@ static char *ffs_prepare_buffer(const char __user *buf, size_t len) data = memdup_user(buf, len); if (IS_ERR(data)) - return ERR_PTR(PTR_ERR(data)); + return data; pr_vdebug("Buffer from user space:\n"); ffs_dump_mem("", data, len); -- cgit v1.2.3 From e21dd90eb86488fb2689c766311eb6fce98b2eb7 Mon Sep 17 00:00:00 2001 From: Salah Triki Date: Tue, 3 Aug 2021 01:53:43 +0100 Subject: usb: misc: adutux: use swap() Use swap() in order to make code cleaner. Issue found by coccinelle. Signed-off-by: Salah Triki Link: https://lore.kernel.org/r/20210803005343.GA1578854@pc Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/adutux.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/misc/adutux.c b/drivers/usb/misc/adutux.c index 6d15a097b007..ed6a19254d2f 100644 --- a/drivers/usb/misc/adutux.c +++ b/drivers/usb/misc/adutux.c @@ -394,13 +394,10 @@ static ssize_t adu_read(struct file *file, __user char *buffer, size_t count, spin_lock_irqsave (&dev->buflock, flags); if (dev->read_buffer_length) { /* we secure access to the primary */ - char *tmp; dev_dbg(&dev->udev->dev, "%s : swap, read_buffer_length = %d\n", __func__, dev->read_buffer_length); - tmp = dev->read_buffer_secondary; - dev->read_buffer_secondary = dev->read_buffer_primary; - dev->read_buffer_primary = tmp; + swap(dev->read_buffer_primary, dev->read_buffer_secondary); dev->secondary_head = 0; dev->secondary_tail = dev->read_buffer_length; dev->read_buffer_length = 0; -- cgit v1.2.3 From 59e477af7b1a2a0d1d4c934f9cfda7b753341f12 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 4 Aug 2021 13:59:07 +0100 Subject: usb: gadget: f_uac2: remove redundant assignments to pointer i_feature Pointer i_feature is being initialized with a value and then immediately re-assigned a new value in the next statement. Fix this by replacing the the redundant initialization with the following assigned value. Acked-by: Felipe Balbi Signed-off-by: Colin Ian King Addresses-Coverity: ("Unused value") Link: https://lore.kernel.org/r/20210804125907.111654-1-colin.king@canonical.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_uac2.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index b9edc6787f79..3c34995276e7 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -970,17 +970,13 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) std_as_in_if1_desc.iInterface = us[STR_AS_IN_ALT1].id; if (FUOUT_EN(uac2_opts)) { - u8 *i_feature = (u8 *)out_feature_unit_desc; - - i_feature = (u8 *)out_feature_unit_desc + - out_feature_unit_desc->bLength - 1; + u8 *i_feature = (u8 *)out_feature_unit_desc + + out_feature_unit_desc->bLength - 1; *i_feature = us[STR_FU_OUT].id; } if (FUIN_EN(uac2_opts)) { - u8 *i_feature = (u8 *)in_feature_unit_desc; - - i_feature = (u8 *)in_feature_unit_desc + - in_feature_unit_desc->bLength - 1; + u8 *i_feature = (u8 *)in_feature_unit_desc + + in_feature_unit_desc->bLength - 1; *i_feature = us[STR_FU_IN].id; } -- cgit v1.2.3 From b8731209958a1dffccc2888121f4c0280c990550 Mon Sep 17 00:00:00 2001 From: Ikjoon Jang Date: Thu, 5 Aug 2021 13:37:47 +0800 Subject: usb: xhci-mtk: Do not use xhci's virt_dev in drop_endpoint xhci-mtk depends on xhci's internal virt_dev when it retrieves its internal data from usb_host_endpoint both in add_endpoint and drop_endpoint callbacks. But when setup packet was retired by transaction errors in xhci_setup_device() path, a virt_dev for the slot is newly created with real_port 0. This leads to xhci-mtks's NULL pointer dereference from drop_endpoint callback as xhci-mtk assumes that virt_dev's real_port is always started from one. The similar problems were addressed by [1] but that can't cover the failure cases from setup_device. This patch drops the usages of xhci's virt_dev in xhci-mtk's drop_endpoint callback by adopting rhashtable for searching mtk's schedule entity from a given usb_host_endpoint pointer instead of searching a linked list. So mtk's drop_endpoint callback doesn't have to rely on virt_dev at all. [1] https://lore.kernel.org/r/1617179142-2681-2-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Ikjoon Jang Link: https://lore.kernel.org/r/20210805133731.1.Icc0f080e75b1312692d4c7c7d25e7df9fe1a05c2@changeid Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mtk-sch.c | 140 ++++++++++++++++++++++------------------ drivers/usb/host/xhci-mtk.h | 15 +++-- 2 files changed, 86 insertions(+), 69 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c index cffcaf4dfa9f..f9b4d27ce449 100644 --- a/drivers/usb/host/xhci-mtk-sch.c +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -80,7 +80,7 @@ decode_ep(struct usb_host_endpoint *ep, enum usb_device_speed speed) interval /= 1000; } - snprintf(buf, DBG_BUF_EN, "%s ep%d%s %s, mpkt:%d, interval:%d/%d%s\n", + snprintf(buf, DBG_BUF_EN, "%s ep%d%s %s, mpkt:%d, interval:%d/%d%s", usb_speed_string(speed), usb_endpoint_num(epd), usb_endpoint_dir_in(epd) ? "in" : "out", usb_ep_type_string(usb_endpoint_type(epd)), @@ -129,6 +129,12 @@ get_bw_info(struct xhci_hcd_mtk *mtk, struct usb_device *udev, int bw_index; virt_dev = xhci->devs[udev->slot_id]; + if (WARN_ONCE(!virt_dev, "xhci-mtk: usb %s has no virt_dev\n", + dev_name(&udev->dev))) + return NULL; + if (WARN_ONCE(!virt_dev->real_port, "xhci-mtk: usb %s has invalid port number\n", + dev_name(&udev->dev))) + return NULL; if (udev->speed >= USB_SPEED_SUPER) { if (usb_endpoint_dir_out(&ep->desc)) @@ -194,7 +200,6 @@ static struct mu3h_sch_tt *find_tt(struct usb_device *udev) } return ERR_PTR(-ENOMEM); } - INIT_LIST_HEAD(&tt->ep_list); *ptt = tt; } @@ -224,7 +229,7 @@ static void drop_tt(struct usb_device *udev) } tt = *ptt; - if (!tt || !list_empty(&tt->ep_list)) + if (!tt || tt->nr_eps > 0) return; /* never allocated , or still in use*/ *ptt = NULL; @@ -236,13 +241,19 @@ static void drop_tt(struct usb_device *udev) } } -static struct mu3h_sch_ep_info *create_sch_ep(struct usb_device *udev, - struct usb_host_endpoint *ep, struct xhci_ep_ctx *ep_ctx) +static struct mu3h_sch_ep_info *create_sch_ep(struct xhci_hcd_mtk *mtk, + 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; + struct mu3h_sch_bw_info *bw_info; + + bw_info = get_bw_info(mtk, udev, ep); + if (!bw_info) + return ERR_PTR(-ENODEV); if (is_fs_or_ls(udev->speed)) len_bw_budget_table = TT_MICROFRAMES_MAX; @@ -266,11 +277,10 @@ static struct mu3h_sch_ep_info *create_sch_ep(struct usb_device *udev, } } + sch_ep->bw_info = bw_info; sch_ep->sch_tt = tt; sch_ep->ep = ep; sch_ep->speed = udev->speed; - INIT_LIST_HEAD(&sch_ep->endpoint); - INIT_LIST_HEAD(&sch_ep->tt_endpoint); return sch_ep; } @@ -552,9 +562,9 @@ static void update_sch_tt(struct mu3h_sch_ep_info *sch_ep, bool used) } if (used) - list_add_tail(&sch_ep->tt_endpoint, &tt->ep_list); - else - list_del(&sch_ep->tt_endpoint); + tt->nr_eps++; + else if (!WARN_ONCE(tt->nr_eps < 1, "unbalanced sch_tt's ep count")) + tt->nr_eps--; } static int load_ep_bw(struct mu3h_sch_bw_info *sch_bw, @@ -585,9 +595,9 @@ static u32 get_esit_boundary(struct mu3h_sch_ep_info *sch_ep) return boundary; } -static int check_sch_bw(struct mu3h_sch_bw_info *sch_bw, - struct mu3h_sch_ep_info *sch_ep) +static int check_sch_bw(struct mu3h_sch_ep_info *sch_ep) { + struct mu3h_sch_bw_info *sch_bw = sch_ep->bw_info; const u32 esit_boundary = get_esit_boundary(sch_ep); const u32 bw_boundary = get_bw_boundary(sch_ep->speed); u32 offset; @@ -633,23 +643,33 @@ static int check_sch_bw(struct mu3h_sch_bw_info *sch_bw, return load_ep_bw(sch_bw, sch_ep, true); } -static void destroy_sch_ep(struct usb_device *udev, - struct mu3h_sch_bw_info *sch_bw, struct mu3h_sch_ep_info *sch_ep) +static const struct rhashtable_params sch_ep_table_param = { + .key_len = sizeof(struct usb_host_endpoint *), + .key_offset = offsetof(struct mu3h_sch_ep_info, ep), + .head_offset = offsetof(struct mu3h_sch_ep_info, ep_link), +}; + +static void destroy_sch_ep(struct xhci_hcd_mtk *mtk, struct usb_device *udev, + struct mu3h_sch_ep_info *sch_ep) { /* only release ep bw check passed by check_sch_bw() */ if (sch_ep->allocated) - load_ep_bw(sch_bw, sch_ep, false); + load_ep_bw(sch_ep->bw_info, sch_ep, false); if (sch_ep->sch_tt) drop_tt(udev); list_del(&sch_ep->endpoint); + rhashtable_remove_fast(&mtk->sch_ep_table, &sch_ep->ep_link, + sch_ep_table_param); kfree(sch_ep); } -static bool need_bw_sch(struct usb_host_endpoint *ep, - enum usb_device_speed speed, int has_tt) +static bool need_bw_sch(struct usb_device *udev, + struct usb_host_endpoint *ep) { + bool has_tt = udev->tt && udev->tt->hub->parent; + /* only for periodic endpoints */ if (usb_endpoint_xfer_control(&ep->desc) || usb_endpoint_xfer_bulk(&ep->desc)) @@ -660,7 +680,7 @@ static bool need_bw_sch(struct usb_host_endpoint *ep, * a TT are also ignored, root-hub will schedule them directly, * but need set @bpkts field of endpoint context to 1. */ - if (is_fs_or_ls(speed) && !has_tt) + if (is_fs_or_ls(udev->speed) && !has_tt) return false; /* skip endpoint with zero maxpkt */ @@ -675,7 +695,12 @@ int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk) struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd); struct mu3h_sch_bw_info *sch_array; int num_usb_bus; - int i; + int ret; + + /* mu3h_sch_ep_info table, 'usb_host_endpoint*' as a key */ + ret = rhashtable_init(&mtk->sch_ep_table, &sch_ep_table_param); + if (ret) + return ret; /* ss IN and OUT are separated */ num_usb_bus = xhci->usb3_rhub.num_ports * 2 + xhci->usb2_rhub.num_ports; @@ -684,9 +709,6 @@ int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk) if (sch_array == NULL) return -ENOMEM; - for (i = 0; i < num_usb_bus; i++) - INIT_LIST_HEAD(&sch_array[i].bw_ep_list); - mtk->sch_array = sch_array; INIT_LIST_HEAD(&mtk->bw_ep_chk_list); @@ -696,6 +718,7 @@ int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk) void xhci_mtk_sch_exit(struct xhci_hcd_mtk *mtk) { + rhashtable_destroy(&mtk->sch_ep_table); kfree(mtk->sch_array); } @@ -713,9 +736,7 @@ static int add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, ep_index = xhci_get_endpoint_index(&ep->desc); ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); - xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed)); - - if (!need_bw_sch(ep, udev->speed, !!virt_dev->tt_info)) { + if (!need_bw_sch(udev, ep)) { /* * set @bpkts to 1 if it is LS or FS periodic endpoint, and its * device does not connected through an external HS hub @@ -727,14 +748,22 @@ static int add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, return 0; } - sch_ep = create_sch_ep(udev, ep, ep_ctx); + xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed)); + + sch_ep = create_sch_ep(mtk, udev, ep, ep_ctx); if (IS_ERR_OR_NULL(sch_ep)) return -ENOMEM; + if (rhashtable_insert_fast(&mtk->sch_ep_table, &sch_ep->ep_link, + sch_ep_table_param)) + return -EEXIST; + setup_sch_info(ep_ctx, sch_ep); list_add_tail(&sch_ep->endpoint, &mtk->bw_ep_chk_list); + xhci_dbg(xhci, "added sch_ep %p : %p\n", sch_ep, ep); + return 0; } @@ -743,25 +772,20 @@ static void drop_ep_quirk(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 xhci_virt_device *virt_dev; - struct mu3h_sch_bw_info *sch_bw; - struct mu3h_sch_ep_info *sch_ep, *tmp; - - virt_dev = xhci->devs[udev->slot_id]; - - xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed)); + struct mu3h_sch_ep_info *sch_ep; + void *key = ep; - if (!need_bw_sch(ep, udev->speed, !!virt_dev->tt_info)) + if (!need_bw_sch(udev, ep)) return; - sch_bw = get_bw_info(mtk, udev, ep); + xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed)); - list_for_each_entry_safe(sch_ep, tmp, &sch_bw->bw_ep_list, endpoint) { - if (sch_ep->ep == ep) { - destroy_sch_ep(udev, sch_bw, sch_ep); - break; - } - } + sch_ep = rhashtable_lookup_fast(&mtk->sch_ep_table, &key, + sch_ep_table_param); + if (sch_ep) + destroy_sch_ep(mtk, udev, sch_ep); + else + xhci_dbg(xhci, "ep %p is not on the bw table\n", ep); } int xhci_mtk_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) @@ -769,30 +793,22 @@ 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 xhci_virt_device *virt_dev = xhci->devs[udev->slot_id]; - struct mu3h_sch_bw_info *sch_bw; - struct mu3h_sch_ep_info *sch_ep, *tmp; + struct mu3h_sch_ep_info *sch_ep; int ret; xhci_dbg(xhci, "%s() udev %s\n", __func__, dev_name(&udev->dev)); list_for_each_entry(sch_ep, &mtk->bw_ep_chk_list, endpoint) { - sch_bw = get_bw_info(mtk, udev, sch_ep->ep); + struct xhci_ep_ctx *ep_ctx; + struct usb_host_endpoint *ep = sch_ep->ep; + unsigned int ep_index = xhci_get_endpoint_index(&ep->desc); - ret = check_sch_bw(sch_bw, sch_ep); + ret = check_sch_bw(sch_ep); if (ret) { xhci_err(xhci, "Not enough bandwidth! (%s)\n", sch_error_string(-ret)); return -ENOSPC; } - } - - list_for_each_entry_safe(sch_ep, tmp, &mtk->bw_ep_chk_list, endpoint) { - struct xhci_ep_ctx *ep_ctx; - struct usb_host_endpoint *ep = sch_ep->ep; - unsigned int ep_index = xhci_get_endpoint_index(&ep->desc); - - sch_bw = get_bw_info(mtk, udev, ep); - list_move_tail(&sch_ep->endpoint, &sch_bw->bw_ep_list); ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); ep_ctx->reserved[0] = cpu_to_le32(EP_BPKTS(sch_ep->pkts) @@ -806,22 +822,23 @@ int xhci_mtk_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) sch_ep->offset, sch_ep->repeat); } - return xhci_check_bandwidth(hcd, udev); + ret = xhci_check_bandwidth(hcd, udev); + if (!ret) + INIT_LIST_HEAD(&mtk->bw_ep_chk_list); + + return ret; } void xhci_mtk_reset_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_bw_info *sch_bw; struct mu3h_sch_ep_info *sch_ep, *tmp; xhci_dbg(xhci, "%s() udev %s\n", __func__, dev_name(&udev->dev)); - list_for_each_entry_safe(sch_ep, tmp, &mtk->bw_ep_chk_list, endpoint) { - sch_bw = get_bw_info(mtk, udev, sch_ep->ep); - destroy_sch_ep(udev, sch_bw, sch_ep); - } + list_for_each_entry_safe(sch_ep, tmp, &mtk->bw_ep_chk_list, endpoint) + destroy_sch_ep(mtk, udev, sch_ep); xhci_reset_bandwidth(hcd, udev); } @@ -850,8 +867,7 @@ int xhci_mtk_drop_ep(struct usb_hcd *hcd, struct usb_device *udev, if (ret) return ret; - if (ep->hcpriv) - drop_ep_quirk(hcd, udev, ep); + drop_ep_quirk(hcd, udev, ep); return 0; } diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h index ace432356c41..ddcf25524f67 100644 --- a/drivers/usb/host/xhci-mtk.h +++ b/drivers/usb/host/xhci-mtk.h @@ -10,6 +10,7 @@ #define _XHCI_MTK_H_ #include +#include #include "xhci.h" @@ -25,36 +26,34 @@ /** * @fs_bus_bw: array to keep track of bandwidth already used for FS - * @ep_list: Endpoints using this TT + * @nr_eps: number of endpoints using this TT */ struct mu3h_sch_tt { u32 fs_bus_bw[XHCI_MTK_MAX_ESIT]; - struct list_head ep_list; + int nr_eps; }; /** * struct mu3h_sch_bw_info: schedule information for bandwidth domain * * @bus_bw: array to keep track of bandwidth already used at each uframes - * @bw_ep_list: eps in the bandwidth domain * * treat a HS root port as a bandwidth domain, but treat a SS root port as * two bandwidth domains, one for IN eps and another for OUT eps. */ struct mu3h_sch_bw_info { u32 bus_bw[XHCI_MTK_MAX_ESIT]; - struct list_head bw_ep_list; }; /** * struct mu3h_sch_ep_info: schedule information for endpoint * + * @bw_info: bandwidth domain which this endpoint belongs * @esit: unit is 125us, equal to 2 << Interval field in ep-context * @num_budget_microframes: number of continuous uframes * (@repeat==1) scheduled within the interval * @bw_cost_per_microframe: bandwidth cost per microframe - * @endpoint: linked into bandwidth domain which it belongs to - * @tt_endpoint: linked into mu3h_sch_tt's list which it belongs to + * @endpoint: linked into bw_ep_chk_list, used by check_bandwidth hook * @sch_tt: mu3h_sch_tt linked into * @ep_type: endpoint type * @maxpkt: max packet size of endpoint @@ -82,11 +81,12 @@ struct mu3h_sch_ep_info { u32 num_budget_microframes; u32 bw_cost_per_microframe; struct list_head endpoint; - struct list_head tt_endpoint; + struct mu3h_sch_bw_info *bw_info; struct mu3h_sch_tt *sch_tt; u32 ep_type; u32 maxpkt; struct usb_host_endpoint *ep; + struct rhash_head ep_link; enum usb_device_speed speed; bool allocated; /* @@ -134,6 +134,7 @@ struct xhci_hcd_mtk { struct device *dev; struct usb_hcd *hcd; struct mu3h_sch_bw_info *sch_array; + struct rhashtable sch_ep_table; struct list_head bw_ep_chk_list; struct mu3c_ippc_regs __iomem *ippc_regs; int num_u2_ports; -- cgit v1.2.3 From 548011957d1d72e0b662300c8b32b81d593b796e Mon Sep 17 00:00:00 2001 From: Ikjoon Jang Date: Thu, 5 Aug 2021 13:39:57 +0800 Subject: usb: xhci-mtk: relax TT periodic bandwidth allocation Currently xhci-mtk needs software-managed bandwidth allocation for periodic endpoints, it allocates the microframe index for the first start-split packet for each endpoint. As this index allocation logic should avoid the conflicts with other full/low-speed periodic endpoints, it uses the worst case byte budgets on high-speed bus bandwidth For example, for an isochronos IN endpoint with 192 bytes budget, it will consume the whole 4 u-frames(188 * 4) while the actual full-speed bus budget should be just 192bytes. This patch changes the low/full-speed bandwidth allocation logic to use "approximate" best case budget for lower speed bandwidth management. For the same endpoint from the above example, the approximate best case budget is now reduced to (188 * 2) bytes. Without this patch, many usb audio headsets with 3 interfaces (audio input, audio output, and HID) cannot be configured on xhci-mtk. Signed-off-by: Ikjoon Jang Link: https://lore.kernel.org/r/20210805133937.1.Ia8174b875bc926c12ce427a5a1415dea31cc35ae@changeid Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mtk-sch.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c index f9b4d27ce449..46cbf5d54f4f 100644 --- a/drivers/usb/host/xhci-mtk-sch.c +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -459,16 +459,17 @@ static int check_fs_bus_bw(struct mu3h_sch_ep_info *sch_ep, int offset) u32 num_esit, tmp; int base; int i, j; + u8 uframes = DIV_ROUND_UP(sch_ep->maxpkt, FS_PAYLOAD_MAX); num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit; + + if (sch_ep->ep_type == INT_IN_EP || sch_ep->ep_type == ISOC_IN_EP) + offset++; + for (i = 0; i < num_esit; i++) { base = offset + i * sch_ep->esit; - /* - * Compared with hs bus, no matter what ep type, - * the hub will always delay one uframe to send data - */ - for (j = 0; j < sch_ep->cs_count; j++) { + for (j = 0; j < uframes; j++) { tmp = tt->fs_bus_bw[base + j] + sch_ep->bw_cost_per_microframe; if (tmp > FS_PAYLOAD_MAX) return -ESCH_BW_OVERFLOW; @@ -546,6 +547,8 @@ static void update_sch_tt(struct mu3h_sch_ep_info *sch_ep, bool used) u32 base, num_esit; int bw_updated; int i, j; + int offset = sch_ep->offset; + u8 uframes = DIV_ROUND_UP(sch_ep->maxpkt, FS_PAYLOAD_MAX); num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit; @@ -554,10 +557,13 @@ static void update_sch_tt(struct mu3h_sch_ep_info *sch_ep, bool used) else bw_updated = -sch_ep->bw_cost_per_microframe; + if (sch_ep->ep_type == INT_IN_EP || sch_ep->ep_type == ISOC_IN_EP) + offset++; + for (i = 0; i < num_esit; i++) { - base = sch_ep->offset + i * sch_ep->esit; + base = offset + i * sch_ep->esit; - for (j = 0; j < sch_ep->cs_count; j++) + for (j = 0; j < uframes; j++) tt->fs_bus_bw[base + j] += bw_updated; } -- cgit v1.2.3 From e390909ac763589558ffb91856f121820f933e4b Mon Sep 17 00:00:00 2001 From: Sanjay R Mehta Date: Fri, 6 Aug 2021 11:59:05 -0500 Subject: thunderbolt: Add vendor specific NHI quirk for auto-clearing interrupt status Introduce nhi_check_quirks() routine to handle any vendor specific quirks to manage a hardware specific implementation. On Intel hardware the USB4 controller supports clearing the interrupt status register automatically right after it is being issued. For this reason add a new quirk that does that on all Intel hardware. Signed-off-by: Basavaraj Natikar Signed-off-by: Sanjay R Mehta Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 33 +++++++++++++++++++++++++-------- include/linux/thunderbolt.h | 2 ++ 2 files changed, 27 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index fa44332845a1..c7a2841ed3b7 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -35,6 +35,8 @@ #define NHI_MAILBOX_TIMEOUT 500 /* ms */ +#define QUIRK_AUTO_CLEAR_INT BIT(0) + static int ring_interrupt_index(struct tb_ring *ring) { int bit = ring->hop; @@ -66,14 +68,17 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) else index = ring->hop + ring->nhi->hop_count; - /* - * Ask the hardware to clear interrupt status bits automatically - * since we already know which interrupt was triggered. - */ - misc = ioread32(ring->nhi->iobase + REG_DMA_MISC); - if (!(misc & REG_DMA_MISC_INT_AUTO_CLEAR)) { - misc |= REG_DMA_MISC_INT_AUTO_CLEAR; - iowrite32(misc, ring->nhi->iobase + REG_DMA_MISC); + if (ring->nhi->quirks & QUIRK_AUTO_CLEAR_INT) { + /* + * Ask the hardware to clear interrupt status + * bits automatically since we already know + * which interrupt was triggered. + */ + misc = ioread32(ring->nhi->iobase + REG_DMA_MISC); + if (!(misc & REG_DMA_MISC_INT_AUTO_CLEAR)) { + misc |= REG_DMA_MISC_INT_AUTO_CLEAR; + iowrite32(misc, ring->nhi->iobase + REG_DMA_MISC); + } } ivr_base = ring->nhi->iobase + REG_INT_VEC_ALLOC_BASE; @@ -1074,6 +1079,16 @@ static void nhi_shutdown(struct tb_nhi *nhi) nhi->ops->shutdown(nhi); } +static void nhi_check_quirks(struct tb_nhi *nhi) +{ + /* + * Intel hardware supports auto clear of the interrupt status + * reqister right after interrupt is being issued. + */ + if (nhi->pdev->vendor == PCI_VENDOR_ID_INTEL) + nhi->quirks |= QUIRK_AUTO_CLEAR_INT; +} + static int nhi_init_msi(struct tb_nhi *nhi) { struct pci_dev *pdev = nhi->pdev; @@ -1190,6 +1205,8 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (!nhi->tx_rings || !nhi->rx_rings) return -ENOMEM; + nhi_check_quirks(nhi); + res = nhi_init_msi(nhi); if (res) { dev_err(&pdev->dev, "cannot enable MSI, aborting\n"); diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index e7c96c37174f..124e13cb1469 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -468,6 +468,7 @@ static inline struct tb_xdomain *tb_service_parent(struct tb_service *svc) * @interrupt_work: Work scheduled to handle ring interrupt when no * MSI-X is used. * @hop_count: Number of rings (end point hops) supported by NHI. + * @quirks: NHI specific quirks if any */ struct tb_nhi { spinlock_t lock; @@ -480,6 +481,7 @@ struct tb_nhi { bool going_away; struct work_struct interrupt_work; u32 hop_count; + unsigned long quirks; }; /** -- cgit v1.2.3 From 7a1808f82a37d638ee6862175e8e5c5433fdd642 Mon Sep 17 00:00:00 2001 From: Sanjay R Mehta Date: Fri, 6 Aug 2021 11:59:06 -0500 Subject: thunderbolt: Handle ring interrupt by reading interrupt status register As per USB4 specification by default "Disable ISR Auto-Clear" bit is set to zero and the Tx/Rx ring interrupt status needs to be cleared. Hence handle it by reading the interrupt status register (ISR) in the MSI-X handler. Signed-off-by: Basavaraj Natikar Signed-off-by: Sanjay R Mehta Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'drivers') diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index c7a2841ed3b7..c73da0532be4 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -382,11 +382,24 @@ void tb_ring_poll_complete(struct tb_ring *ring) } EXPORT_SYMBOL_GPL(tb_ring_poll_complete); +static void ring_clear_msix(const struct tb_ring *ring) +{ + if (ring->nhi->quirks & QUIRK_AUTO_CLEAR_INT) + return; + + if (ring->is_tx) + ioread32(ring->nhi->iobase + REG_RING_NOTIFY_BASE); + else + ioread32(ring->nhi->iobase + REG_RING_NOTIFY_BASE + + 4 * (ring->nhi->hop_count / 32)); +} + static irqreturn_t ring_msix(int irq, void *data) { struct tb_ring *ring = data; spin_lock(&ring->nhi->lock); + ring_clear_msix(ring); spin_lock(&ring->lock); __ring_interrupt(ring); spin_unlock(&ring->lock); -- cgit v1.2.3 From fb7a89ad2f048489605611801d480c7bb9d03f94 Mon Sep 17 00:00:00 2001 From: Sanjay R Mehta Date: Tue, 3 Aug 2021 07:34:55 -0500 Subject: thunderbolt: Do not read control adapter config space Adapter 0 is the control adapter and as per USB4 spec in section 2.2.6.2 control Adapters do not have an adapter configuration space. For this reason skip reading adapter config space in tb_port_init() when the port number is 0. This actually simplifies the rest of the function as we don't need to check for the port->port == 0 anymore. While there drop the extra empty line at the end of the function. Signed-off-by: Basavaraj Natikar Signed-off-by: Sanjay R Mehta Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index bc91887f24c3..2c44b24bc5d0 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -724,6 +724,12 @@ static int tb_init_port(struct tb_port *port) int res; int cap; + INIT_LIST_HEAD(&port->list); + + /* Control adapter does not have configuration space */ + if (!port->port) + return 0; + res = tb_port_read(port, &port->config, TB_CFG_PORT, 0, 8); if (res) { if (res == -ENODEV) { @@ -736,7 +742,7 @@ static int tb_init_port(struct tb_port *port) } /* Port 0 is the switch itself and has no PHY. */ - if (port->config.type == TB_TYPE_PORT && port->port != 0) { + if (port->config.type == TB_TYPE_PORT) { cap = tb_port_find_cap(port, TB_PORT_CAP_PHY); if (cap > 0) @@ -762,7 +768,7 @@ static int tb_init_port(struct tb_port *port) if (!port->ctl_credits) port->ctl_credits = 2; - } else if (port->port != 0) { + } else { cap = tb_port_find_cap(port, TB_PORT_CAP_ADAP); if (cap > 0) port->cap_adap = cap; @@ -773,10 +779,7 @@ static int tb_init_port(struct tb_port *port) ADP_CS_4_TOTAL_BUFFERS_SHIFT; tb_dump_port(port->sw->tb, port); - - INIT_LIST_HEAD(&port->list); return 0; - } static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid, -- cgit v1.2.3 From 42716425ad7e1b6529ec61c260c11176841f4b5f Mon Sep 17 00:00:00 2001 From: Sanjay R Mehta Date: Tue, 3 Aug 2021 07:34:56 -0500 Subject: thunderbolt: Fix port linking by checking all adapters In tb_switch_default_link_ports(), while linking of ports, only odd-numbered ports (1,3,5..) are considered and even-numbered ports are not considered. AMD host router has lane adapters at 2 and 3 and link ports at adapter 2 is not considered due to which lane bonding gets disabled. Hence added a fix such that all ports are considered during linking of ports. Signed-off-by: Basavaraj Natikar Signed-off-by: Sanjay R Mehta Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 2c44b24bc5d0..02774b0764e7 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -2466,7 +2466,7 @@ static void tb_switch_default_link_ports(struct tb_switch *sw) { int i; - for (i = 1; i <= sw->config.max_port_number; i += 2) { + for (i = 1; i <= sw->config.max_port_number; i++) { struct tb_port *port = &sw->ports[i]; struct tb_port *subordinate; -- cgit v1.2.3 From 5324bad66f09fdade4d72f3b8b55caf595557e3d Mon Sep 17 00:00:00 2001 From: Argishti Aleksanyan Date: Tue, 10 Aug 2021 06:42:58 -0400 Subject: usb: dwc2: gadget: implement udc_set_speed() Implemented udc_set_speed() gadget ops to allow the udc to select the gadget speed on initialization. Acked-by: Minas Harutyunyan Signed-off-by: Argishti Aleksanyan Link: https://lore.kernel.org/r/c453469d618100321c876a8c2b0ebee15a456eac.1628583235.git.aleksan@synopsys.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/gadget.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 985b272f53d5..837237e4bc96 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -4709,12 +4709,35 @@ static int dwc2_hsotg_vbus_draw(struct usb_gadget *gadget, unsigned int mA) return usb_phy_set_power(hsotg->uphy, mA); } +static void dwc2_gadget_set_speed(struct usb_gadget *g, enum usb_device_speed speed) +{ + struct dwc2_hsotg *hsotg = to_hsotg(g); + unsigned long flags; + + spin_lock_irqsave(&hsotg->lock, flags); + switch (speed) { + case USB_SPEED_HIGH: + hsotg->params.speed = DWC2_SPEED_PARAM_HIGH; + break; + case USB_SPEED_FULL: + hsotg->params.speed = DWC2_SPEED_PARAM_FULL; + break; + case USB_SPEED_LOW: + hsotg->params.speed = DWC2_SPEED_PARAM_LOW; + break; + default: + dev_err(hsotg->dev, "invalid speed (%d)\n", speed); + } + spin_unlock_irqrestore(&hsotg->lock, flags); +} + static const struct usb_gadget_ops dwc2_hsotg_gadget_ops = { .get_frame = dwc2_hsotg_gadget_getframe, .set_selfpowered = dwc2_hsotg_set_selfpowered, .udc_start = dwc2_hsotg_udc_start, .udc_stop = dwc2_hsotg_udc_stop, .pullup = dwc2_hsotg_pullup, + .udc_set_speed = dwc2_gadget_set_speed, .vbus_session = dwc2_hsotg_vbus_session, .vbus_draw = dwc2_hsotg_vbus_draw, }; -- cgit v1.2.3 From baa2986bda3f7b2386607587a4185e3dff8f98df Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Mon, 9 Aug 2021 23:21:14 +0300 Subject: usb: dwc3: meson-g12a: add IRQ check The driver neglects to check the result of platform_get_irq()'s call and blithely passes the negative error codes to devm_request_threaded_irq() (which takes *unsigned* IRQ #), causing it to fail with -EINVAL, overriding an original error code. Stop calling devm_request_threaded_irq() with the invalid IRQ #s. Fixes: f90db10779ad ("usb: dwc3: meson-g12a: Add support for IRQ based OTG switching") Reviewed-by: Martin Blumenstingl Acked-by: Felipe Balbi Signed-off-by: Sergey Shtylyov Link: https://lore.kernel.org/r/96106462-5538-0b2f-f2ab-ee56e4853912@omp.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-meson-g12a.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc3/dwc3-meson-g12a.c b/drivers/usb/dwc3/dwc3-meson-g12a.c index ffe301d6ea35..d0f9b7c296b0 100644 --- a/drivers/usb/dwc3/dwc3-meson-g12a.c +++ b/drivers/usb/dwc3/dwc3-meson-g12a.c @@ -598,6 +598,8 @@ static int dwc3_meson_g12a_otg_init(struct platform_device *pdev, USB_R5_ID_DIG_IRQ, 0); irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, dwc3_meson_g12a_irq_thread, IRQF_ONESHOT, pdev->name, priv); -- cgit v1.2.3 From 175006956740f70ca23394c58f8d7804776741bd Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Mon, 9 Aug 2021 23:23:51 +0300 Subject: usb: dwc3: qcom: add IRQ check In dwc3_qcom_acpi_register_core(), the driver neglects to check the result of platform_get_irq()'s call and blithely assigns the negative error codes to the allocated child device's IRQ resource and then passing this resource to platform_device_add_resources() and later causing dwc3_otg_get_irq() to fail anyway. Stop calling platform_device_add_resources() with the invalid IRQ #s, so that there's less complexity in the IRQ error checking. Fixes: 2bc02355f8ba ("usb: dwc3: qcom: Add support for booting with ACPI") Acked-by: Felipe Balbi Signed-off-by: Sergey Shtylyov Link: https://lore.kernel.org/r/45fec3da-1679-5bfe-5d74-219ca3fb28e7@omp.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 1c440732db22..9abbd01028c5 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -614,6 +614,10 @@ static int dwc3_qcom_acpi_register_core(struct platform_device *pdev) qcom->acpi_pdata->dwc3_core_base_size; irq = platform_get_irq(pdev_irq, 0); + if (irq < 0) { + ret = irq; + goto out; + } child_res[1].flags = IORESOURCE_IRQ; child_res[1].start = child_res[1].end = irq; -- cgit v1.2.3 From 50855c31573b02963f0aa2aacfd4ea41c31ae0e0 Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Mon, 9 Aug 2021 23:27:28 +0300 Subject: usb: gadget: udc: at91: add IRQ check The driver neglects to check the result of platform_get_irq()'s call and blithely passes the negative error codes to devm_request_irq() (which takes *unsigned* IRQ #), causing it to fail with -EINVAL, overriding an original error code. Stop calling devm_request_irq() with the invalid IRQ #s. Fixes: 8b2e76687b39 ("USB: AT91 UDC updates, mostly power management") Signed-off-by: Sergey Shtylyov Acked-by: Felipe Balbi Link: https://lore.kernel.org/r/6654a224-739a-1a80-12f0-76d920f87b6c@omp.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/at91_udc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/at91_udc.c b/drivers/usb/gadget/udc/at91_udc.c index eede5cedacb4..d9ad9adf7348 100644 --- a/drivers/usb/gadget/udc/at91_udc.c +++ b/drivers/usb/gadget/udc/at91_udc.c @@ -1876,7 +1876,9 @@ static int at91udc_probe(struct platform_device *pdev) clk_disable(udc->iclk); /* request UDC and maybe VBUS irqs */ - udc->udp_irq = platform_get_irq(pdev, 0); + udc->udp_irq = retval = platform_get_irq(pdev, 0); + if (retval < 0) + goto err_unprepare_iclk; retval = devm_request_irq(dev, udc->udp_irq, at91_udc_irq, 0, driver_name, udc); if (retval) { -- cgit v1.2.3 From ecff88e819e31081d41cd05bb199b9bd10e13e90 Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Mon, 9 Aug 2021 23:35:11 +0300 Subject: usb: gadget: udc: s3c2410: add IRQ check The driver neglects to check the result of platform_get_irq()'s call and blithely passes the negative error codes to request_irq() (which takes *unsigned* IRQ #), causing it to fail with -EINVAL, overriding an original error code. Stop calling request_irq() with the invalid IRQ #s. Fixes: 188db4435ac6 ("usb: gadget: s3c: use platform resources") Reviewed-by: Krzysztof Kozlowski Acked-by: Felipe Balbi Signed-off-by: Sergey Shtylyov Link: https://lore.kernel.org/r/bd69b22c-b484-5a1f-c798-78d4b78405f2@omp.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/s3c2410_udc.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/s3c2410_udc.c b/drivers/usb/gadget/udc/s3c2410_udc.c index 179777cb699f..e3931da24277 100644 --- a/drivers/usb/gadget/udc/s3c2410_udc.c +++ b/drivers/usb/gadget/udc/s3c2410_udc.c @@ -1784,6 +1784,10 @@ static int s3c2410_udc_probe(struct platform_device *pdev) s3c2410_udc_reinit(udc); irq_usbd = platform_get_irq(pdev, 0); + if (irq_usbd < 0) { + retval = irq_usbd; + goto err_udc_clk; + } /* irq setup after old hardware state is cleaned up */ retval = request_irq(irq_usbd, s3c2410_udc_irq, -- cgit v1.2.3 From 711087f342914e831269438ff42cf59bb0142c71 Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Mon, 9 Aug 2021 23:45:15 +0300 Subject: usb: misc: brcmstb-usb-pinmap: add IRQ check The driver neglects to check the result of platform_get_irq()'s call and blithely passes the negative error codes to devm_request_irq() (which takes *unsigned* IRQ #), causing it to fail with -EINVAL, overriding an original error code. Stop calling devm_request_irq() with the invalid IRQ #s. Fixes: 517c4c44b323 ("usb: Add driver to allow any GPIO to be used for 7211 USB signals") Reviewed-by: Florian Fainelli Signed-off-by: Sergey Shtylyov Link: https://lore.kernel.org/r/806d0b1a-365b-93d9-3fc1-922105ca5e61@omp.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/brcmstb-usb-pinmap.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/misc/brcmstb-usb-pinmap.c b/drivers/usb/misc/brcmstb-usb-pinmap.c index 336653091e3b..2b2019c19cde 100644 --- a/drivers/usb/misc/brcmstb-usb-pinmap.c +++ b/drivers/usb/misc/brcmstb-usb-pinmap.c @@ -293,6 +293,8 @@ static int __init brcmstb_usb_pinmap_probe(struct platform_device *pdev) /* Enable interrupt for out pins */ irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; err = devm_request_irq(&pdev->dev, irq, brcmstb_usb_pinmap_ovr_isr, IRQF_TRIGGER_RISING, -- cgit v1.2.3 From ecc2f30dbb25969908115c81ec23650ed982b004 Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Mon, 9 Aug 2021 23:50:18 +0300 Subject: usb: phy: fsl-usb: add IRQ check The driver neglects to check the result of platform_get_irq()'s call and blithely passes the negative error codes to request_irq() (which takes *unsigned* IRQ #), causing it to fail with -EINVAL, overriding an original error code. Stop calling request_irq() with the invalid IRQ #s. Fixes: 0807c500a1a6 ("USB: add Freescale USB OTG Transceiver driver") Acked-by: Felipe Balbi Signed-off-by: Sergey Shtylyov Link: https://lore.kernel.org/r/b0a86089-8b8b-122e-fd6d-73e8c2304964@omp.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-fsl-usb.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/phy/phy-fsl-usb.c b/drivers/usb/phy/phy-fsl-usb.c index f34c9437a182..972704262b02 100644 --- a/drivers/usb/phy/phy-fsl-usb.c +++ b/drivers/usb/phy/phy-fsl-usb.c @@ -873,6 +873,8 @@ int usb_otg_start(struct platform_device *pdev) /* request irq */ p_otg->irq = platform_get_irq(pdev, 0); + if (p_otg->irq < 0) + return p_otg->irq; status = request_irq(p_otg->irq, fsl_otg_isr, IRQF_SHARED, driver_name, p_otg); if (status) { -- cgit v1.2.3 From 0881e22c06e66af0b64773c91c8868ead3d01aa1 Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Mon, 9 Aug 2021 23:53:16 +0300 Subject: usb: phy: twl6030: add IRQ checks The driver neglects to check the result of platform_get_irq()'s calls and blithely passes the negative error codes to request_threaded_irq() (which takes *unsigned* IRQ #), causing them both to fail with -EINVAL, overriding an original error code. Stop calling request_threaded_irq() with the invalid IRQ #s. Fixes: c33fad0c3748 ("usb: otg: Adding twl6030-usb transceiver driver for OMAP4430") Acked-by: Felipe Balbi Signed-off-by: Sergey Shtylyov Link: https://lore.kernel.org/r/9507f50b-50f1-6dc4-f57c-3ed4e53a1c25@omp.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-twl6030-usb.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/phy/phy-twl6030-usb.c b/drivers/usb/phy/phy-twl6030-usb.c index 8ba6c5a91557..ab3c38a7d8ac 100644 --- a/drivers/usb/phy/phy-twl6030-usb.c +++ b/drivers/usb/phy/phy-twl6030-usb.c @@ -348,6 +348,11 @@ static int twl6030_usb_probe(struct platform_device *pdev) twl->irq2 = platform_get_irq(pdev, 1); twl->linkstat = MUSB_UNKNOWN; + if (twl->irq1 < 0) + return twl->irq1; + if (twl->irq2 < 0) + return twl->irq2; + twl->comparator.set_vbus = twl6030_set_vbus; twl->comparator.start_srp = twl6030_start_srp; -- cgit v1.2.3 From e88f28514065a6c48aadc367efb0ef6378a01543 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 13 Aug 2021 14:30:47 +0800 Subject: usb: mtu3: restore HS function when set SS/SSP Due to HS function is disabled when set as FS, need restore it when set as SS/SSP. Fixes: dc4c1aa7eae9 ("usb: mtu3: add ->udc_set_speed()") Cc: stable@vger.kernel.org Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1628836253-7432-1-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c index f90e5cdec614..c4a2c37abf62 100644 --- a/drivers/usb/mtu3/mtu3_core.c +++ b/drivers/usb/mtu3/mtu3_core.c @@ -249,11 +249,13 @@ static void mtu3_set_speed(struct mtu3 *mtu, enum usb_device_speed speed) mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE); break; case USB_SPEED_SUPER: + mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE); mtu3_clrbits(mtu->ippc_base, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_SSP_SPEED); break; case USB_SPEED_SUPER_PLUS: - mtu3_setbits(mtu->ippc_base, SSUSB_U3_CTRL(0), + mtu3_setbits(mbase, U3D_POWER_MANAGEMENT, HS_ENABLE); + mtu3_setbits(mtu->ippc_base, SSUSB_U3_CTRL(0), SSUSB_U3_PORT_SSP_SPEED); break; default: -- cgit v1.2.3 From fd7cb394ec7efccc3985feb0978cee4d352e1817 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 13 Aug 2021 14:30:48 +0800 Subject: usb: mtu3: use @mult for HS isoc or intr For HS isoc or intr, should use @mult but not @burst to save mult value. Fixes: 4d79e042ed8b ("usb: mtu3: add support for usb3.1 IP") Cc: stable@vger.kernel.org Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1628836253-7432-2-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_gadget.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3_gadget.c b/drivers/usb/mtu3/mtu3_gadget.c index 7b54631ca335..45e7d2e3b04b 100644 --- a/drivers/usb/mtu3/mtu3_gadget.c +++ b/drivers/usb/mtu3/mtu3_gadget.c @@ -92,7 +92,7 @@ static int mtu3_ep_enable(struct mtu3_ep *mep) usb_endpoint_xfer_int(desc)) { interval = desc->bInterval; interval = clamp_val(interval, 1, 16) - 1; - burst = (max_packet & GENMASK(12, 11)) >> 11; + mult = (max_packet & GENMASK(12, 11)) >> 11; } break; default: -- cgit v1.2.3 From 44e4439d8f9f8d0e9da767d1f31e7c211081feca Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 13 Aug 2021 14:30:49 +0800 Subject: usb: mtu3: fix the wrong HS mult value usb_endpoint_maxp() returns actual max packet size, @mult will always be zero, fix it by using usb_endpoint_maxp_mult() instead to get mult. Fixes: 4d79e042ed8b ("usb: mtu3: add support for usb3.1 IP") Cc: stable@vger.kernel.org Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1628836253-7432-3-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_gadget.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3_gadget.c b/drivers/usb/mtu3/mtu3_gadget.c index 45e7d2e3b04b..a9a65b4bbfed 100644 --- a/drivers/usb/mtu3/mtu3_gadget.c +++ b/drivers/usb/mtu3/mtu3_gadget.c @@ -64,14 +64,12 @@ static int mtu3_ep_enable(struct mtu3_ep *mep) u32 interval = 0; u32 mult = 0; u32 burst = 0; - int max_packet; int ret; desc = mep->desc; comp_desc = mep->comp_desc; mep->type = usb_endpoint_type(desc); - max_packet = usb_endpoint_maxp(desc); - mep->maxp = max_packet & GENMASK(10, 0); + mep->maxp = usb_endpoint_maxp(desc); switch (mtu->g.speed) { case USB_SPEED_SUPER: @@ -92,7 +90,7 @@ static int mtu3_ep_enable(struct mtu3_ep *mep) usb_endpoint_xfer_int(desc)) { interval = desc->bInterval; interval = clamp_val(interval, 1, 16) - 1; - mult = (max_packet & GENMASK(12, 11)) >> 11; + mult = usb_endpoint_maxp_mult(desc) - 1; } break; default: -- cgit v1.2.3 From e9ab75f26eb9354dfc03aea3401b8cfb42cd6718 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 13 Aug 2021 14:30:50 +0800 Subject: usb: cdnsp: fix the wrong mult value for HS isoc or intr usb_endpoint_maxp() only returns the bit[10:0] of wMaxPacketSize of endpoint descriptor, not include bit[12:11] anymore, so use usb_endpoint_maxp_mult() instead. Fixes: 3d82904559f4 ("usb: cdnsp: cdns3 Add main part of Cadence USBSSP DRD Driver") Cc: stable@vger.kernel.org Acked-by: Felipe Balbi Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1628836253-7432-4-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdnsp-mem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/cdns3/cdnsp-mem.c b/drivers/usb/cdns3/cdnsp-mem.c index a47948a1623f..ad9aee3f1e39 100644 --- a/drivers/usb/cdns3/cdnsp-mem.c +++ b/drivers/usb/cdns3/cdnsp-mem.c @@ -882,7 +882,7 @@ static u32 cdnsp_get_endpoint_max_burst(struct usb_gadget *g, if (g->speed == USB_SPEED_HIGH && (usb_endpoint_xfer_isoc(pep->endpoint.desc) || usb_endpoint_xfer_int(pep->endpoint.desc))) - return (usb_endpoint_maxp(pep->endpoint.desc) & 0x1800) >> 11; + return usb_endpoint_maxp_mult(pep->endpoint.desc) - 1; return 0; } -- cgit v1.2.3 From eeb0cfb6b2b6b731902e68af641e30bd31be3c7b Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 13 Aug 2021 14:30:51 +0800 Subject: usb: gadget: tegra-xudc: fix the wrong mult value for HS isoc or intr usb_endpoint_maxp() only returns the bit[10:0] of wMaxPacketSize of endpoint descriptor, not includes bit[12:11] anymore, so use usb_endpoint_maxp_mult() instead. Meanwhile no need AND 0x7ff when get maxp, remove it. Fixes: 49db427232fe ("usb: gadget: Add UDC driver for tegra XUSB device mode controller") Cc: stable@vger.kernel.org Acked-by: Felipe Balbi Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1628836253-7432-5-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/tegra-xudc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index c0ca7144e512..43f1b0d461c1 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -1610,7 +1610,7 @@ static void tegra_xudc_ep_context_setup(struct tegra_xudc_ep *ep) u16 maxpacket, maxburst = 0, esit = 0; u32 val; - maxpacket = usb_endpoint_maxp(desc) & 0x7ff; + maxpacket = usb_endpoint_maxp(desc); if (xudc->gadget.speed == USB_SPEED_SUPER) { if (!usb_endpoint_xfer_control(desc)) maxburst = comp_desc->bMaxBurst; @@ -1621,7 +1621,7 @@ static void tegra_xudc_ep_context_setup(struct tegra_xudc_ep *ep) (usb_endpoint_xfer_int(desc) || usb_endpoint_xfer_isoc(desc))) { if (xudc->gadget.speed == USB_SPEED_HIGH) { - maxburst = (usb_endpoint_maxp(desc) >> 11) & 0x3; + maxburst = usb_endpoint_maxp_mult(desc) - 1; if (maxburst == 0x3) { dev_warn(xudc->dev, "invalid endpoint maxburst\n"); -- cgit v1.2.3 From b553c9466fa54d29e314659117b0b24719f24bd3 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 13 Aug 2021 14:30:52 +0800 Subject: usb: gadget: bdc: remove unnecessary AND operation when get ep maxp usb_endpoint_maxp() already returns actual max packet size, no need to AND 0x7ff. Acked-by: Felipe Balbi Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1628836253-7432-6-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc_cmd.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/bdc/bdc_cmd.c b/drivers/usb/gadget/udc/bdc/bdc_cmd.c index 995f79c79f96..67887316a1a6 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_cmd.c +++ b/drivers/usb/gadget/udc/bdc/bdc_cmd.c @@ -153,7 +153,6 @@ int bdc_config_ep(struct bdc *bdc, struct bdc_ep *ep) si = clamp_val(si, 1, 16) - 1; mps = usb_endpoint_maxp(desc); - mps &= 0x7ff; param2 |= mps << MP_SHIFT; param2 |= usb_endpoint_type(desc) << EPT_SHIFT; -- cgit v1.2.3 From e9e6e164ed8f6d017f08b426cfc936bb70ec6d5d Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Wed, 4 Aug 2021 16:19:17 +0800 Subject: usb: typec: tcpm: Support non-PD mode Even if the Type-C controller supports PD, it is doable to disable PD capabilities with the current state machine in TCPM. Without enabling RX in low-level drivers and with skipping the power negotiation, the port is eligible to be a non-PD Type-C port. Use new flags whose values are populated from the device tree to decide the port PD capability. Adding "pd-disable" property in device tree indicates that the port does not support PD. If PD is not supported, the device tree property "typec-power-opmode" shall be added to specify the advertised Rp value if the port supports SRC role. Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Kyle Tso Link: https://lore.kernel.org/r/20210804081917.3390341-3-kyletso@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 87 +++++++++++++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 19 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index b9bb63d749ec..c40e0513873d 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -316,6 +316,7 @@ struct tcpm_port { struct typec_partner *partner; enum typec_cc_status cc_req; + enum typec_cc_status src_rp; /* work only if pd_supported == false */ enum typec_cc_status cc1; enum typec_cc_status cc2; @@ -323,6 +324,7 @@ struct tcpm_port { bool attached; bool connected; + bool pd_supported; enum typec_port_type port_type; /* @@ -815,6 +817,9 @@ static enum typec_cc_status tcpm_rp_cc(struct tcpm_port *port) int nr_pdo = port->nr_src_pdo; int i; + if (!port->pd_supported) + return port->src_rp; + /* * Search for first entry with matching voltage. * It should report the maximum supported current. @@ -3568,9 +3573,11 @@ static int tcpm_src_attach(struct tcpm_port *port) if (ret < 0) return ret; - ret = port->tcpc->set_pd_rx(port->tcpc, true); - if (ret < 0) - goto out_disable_mux; + if (port->pd_supported) { + ret = port->tcpc->set_pd_rx(port->tcpc, true); + if (ret < 0) + goto out_disable_mux; + } /* * USB Type-C specification, version 1.2, @@ -3600,7 +3607,8 @@ static int tcpm_src_attach(struct tcpm_port *port) out_disable_vconn: tcpm_set_vconn(port, false); out_disable_pd: - port->tcpc->set_pd_rx(port->tcpc, false); + if (port->pd_supported) + port->tcpc->set_pd_rx(port->tcpc, false); out_disable_mux: tcpm_mux_set(port, TYPEC_STATE_SAFE, USB_ROLE_NONE, TYPEC_ORIENTATION_NONE); @@ -3804,6 +3812,20 @@ static enum typec_pwr_opmode tcpm_get_pwr_opmode(enum typec_cc_status cc) } } +static enum typec_cc_status tcpm_pwr_opmode_to_rp(enum typec_pwr_opmode opmode) +{ + switch (opmode) { + case TYPEC_PWR_MODE_USB: + return TYPEC_CC_RP_DEF; + case TYPEC_PWR_MODE_1_5A: + return TYPEC_CC_RP_1_5; + case TYPEC_PWR_MODE_3_0A: + case TYPEC_PWR_MODE_PD: + default: + return TYPEC_CC_RP_3_0; + } +} + static void run_state_machine(struct tcpm_port *port) { int ret; @@ -3914,6 +3936,10 @@ static void run_state_machine(struct tcpm_port *port) if (port->ams == POWER_ROLE_SWAP || port->ams == FAST_ROLE_SWAP) tcpm_ams_finish(port); + if (!port->pd_supported) { + tcpm_set_state(port, SRC_READY, 0); + break; + } port->upcoming_state = SRC_SEND_CAPABILITIES; tcpm_ams_start(port, POWER_NEGOTIATION); break; @@ -4161,7 +4187,10 @@ static void run_state_machine(struct tcpm_port *port) current_lim = PD_P_SNK_STDBY_MW / 5; tcpm_set_current_limit(port, current_lim, 5000); tcpm_set_charge(port, true); - tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0); + if (!port->pd_supported) + tcpm_set_state(port, SNK_READY, 0); + else + tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0); break; } /* @@ -4389,7 +4418,8 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_vbus(port, true); if (port->ams == HARD_RESET) tcpm_ams_finish(port); - port->tcpc->set_pd_rx(port->tcpc, true); + if (port->pd_supported) + port->tcpc->set_pd_rx(port->tcpc, true); tcpm_set_attached_state(port, true); tcpm_set_state(port, SRC_UNATTACHED, PD_T_PS_SOURCE_ON); break; @@ -5898,6 +5928,7 @@ EXPORT_SYMBOL_GPL(tcpm_tcpc_reset); static int tcpm_fw_get_caps(struct tcpm_port *port, struct fwnode_handle *fwnode) { + const char *opmode_str; const char *cap_str; int ret; u32 mw, frs_current; @@ -5932,22 +5963,37 @@ static int tcpm_fw_get_caps(struct tcpm_port *port, return ret; port->typec_caps.type = ret; port->port_type = port->typec_caps.type; + port->pd_supported = !fwnode_property_read_bool(fwnode, "pd-disable"); port->slow_charger_loop = fwnode_property_read_bool(fwnode, "slow-charger-loop"); if (port->port_type == TYPEC_PORT_SNK) goto sink; - /* Get source pdos */ - ret = fwnode_property_count_u32(fwnode, "source-pdos"); - if (ret <= 0) - return -EINVAL; + /* Get Source PDOs for the PD port or Source Rp value for the non-PD port */ + if (port->pd_supported) { + ret = fwnode_property_count_u32(fwnode, "source-pdos"); + if (ret == 0) + return -EINVAL; + else if (ret < 0) + return ret; - port->nr_src_pdo = min(ret, PDO_MAX_OBJECTS); - ret = fwnode_property_read_u32_array(fwnode, "source-pdos", - port->src_pdo, port->nr_src_pdo); - if ((ret < 0) || tcpm_validate_caps(port, port->src_pdo, - port->nr_src_pdo)) - return -EINVAL; + port->nr_src_pdo = min(ret, PDO_MAX_OBJECTS); + ret = fwnode_property_read_u32_array(fwnode, "source-pdos", + port->src_pdo, port->nr_src_pdo); + if (ret) + return ret; + ret = tcpm_validate_caps(port, port->src_pdo, port->nr_src_pdo); + if (ret) + return ret; + } else { + ret = fwnode_property_read_string(fwnode, "typec-power-opmode", &opmode_str); + if (ret) + return ret; + ret = typec_find_pwr_opmode(opmode_str); + if (ret < 0) + return ret; + port->src_rp = tcpm_pwr_opmode_to_rp(ret); + } if (port->port_type == TYPEC_PORT_SRC) return 0; @@ -5961,6 +6007,11 @@ static int tcpm_fw_get_caps(struct tcpm_port *port, if (port->typec_caps.prefer_role < 0) return -EINVAL; sink: + port->self_powered = fwnode_property_read_bool(fwnode, "self-powered"); + + if (!port->pd_supported) + return 0; + /* Get sink pdos */ ret = fwnode_property_count_u32(fwnode, "sink-pdos"); if (ret <= 0) @@ -5977,9 +6028,7 @@ sink: return -EINVAL; port->operating_snk_mw = mw / 1000; - port->self_powered = fwnode_property_read_bool(fwnode, "self-powered"); - - /* FRS can only be supported byb DRP ports */ + /* FRS can only be supported by DRP ports */ if (port->port_type == TYPEC_PORT_DRP) { ret = fwnode_property_read_u32(fwnode, "new-source-frs-typec-current", &frs_current); -- cgit v1.2.3 From cea45a3bd2dd4d9c35581328f571afd32b3c9f48 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 11 Aug 2021 17:52:54 +0200 Subject: usb: gadget: udc: renesas_usb3: Fix soc_device_match() abuse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit soc_device_match() is intended as a last resort, to handle e.g. quirks that cannot be handled by matching based on a compatible value. As the device nodes for the Renesas USB 3.0 Peripheral Controller on R-Car E3 and RZ/G2E do have SoC-specific compatible values, the latter can and should be used to match against these devices. This also fixes support for the USB 3.0 Peripheral Controller on the R-Car E3e (R8A779M6) SoC, which is a different grading of the R-Car E3 (R8A77990) SoC, using the same SoC-specific compatible value. Fixes: 30025efa8b5e75f5 ("usb: gadget: udc: renesas_usb3: add support for r8a77990") Fixes: 546970fdab1da5fe ("usb: gadget: udc: renesas_usb3: add support for r8a774c0") Reviewed-by: Niklas Söderlund Signed-off-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/760981fb4cd110d7cbfc9dcffa365e7c8b25c6e5.1628696960.git.geert+renesas@glider.be Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/renesas_usb3.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c index f1b35a39d1ba..57d417a7c3e0 100644 --- a/drivers/usb/gadget/udc/renesas_usb3.c +++ b/drivers/usb/gadget/udc/renesas_usb3.c @@ -2707,10 +2707,15 @@ static const struct renesas_usb3_priv renesas_usb3_priv_r8a77990 = { static const struct of_device_id usb3_of_match[] = { { + .compatible = "renesas,r8a774c0-usb3-peri", + .data = &renesas_usb3_priv_r8a77990, + }, { .compatible = "renesas,r8a7795-usb3-peri", .data = &renesas_usb3_priv_gen3, - }, - { + }, { + .compatible = "renesas,r8a77990-usb3-peri", + .data = &renesas_usb3_priv_r8a77990, + }, { .compatible = "renesas,rcar-gen3-usb3-peri", .data = &renesas_usb3_priv_gen3, }, @@ -2719,18 +2724,10 @@ static const struct of_device_id usb3_of_match[] = { MODULE_DEVICE_TABLE(of, usb3_of_match); static const struct soc_device_attribute renesas_usb3_quirks_match[] = { - { - .soc_id = "r8a774c0", - .data = &renesas_usb3_priv_r8a77990, - }, { .soc_id = "r8a7795", .revision = "ES1.*", .data = &renesas_usb3_priv_r8a7795_es1, }, - { - .soc_id = "r8a77990", - .data = &renesas_usb3_priv_r8a77990, - }, { /* sentinel */ }, }; -- cgit v1.2.3 From 4ac5132e8a4300637a2da8f5d6bc7650db735b8a Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Fri, 13 Aug 2021 23:30:18 +0300 Subject: usb: host: ohci-tmio: add IRQ check The driver neglects to check the result of platform_get_irq()'s call and blithely passes the negative error codes to usb_add_hcd() (which takes *unsigned* IRQ #), causing request_irq() that it calls to fail with -EINVAL, overriding an original error code. Stop calling usb_add_hcd() with the invalid IRQ #s. Fixes: 78c73414f4f6 ("USB: ohci: add support for tmio-ohci cell") Acked-by: Alan Stern Signed-off-by: Sergey Shtylyov Link: https://lore.kernel.org/r/402e1a45-a0a4-0e08-566a-7ca1331506b1@omp.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-tmio.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/host/ohci-tmio.c b/drivers/usb/host/ohci-tmio.c index 7f857bad9e95..08ec2ab0d95a 100644 --- a/drivers/usb/host/ohci-tmio.c +++ b/drivers/usb/host/ohci-tmio.c @@ -202,6 +202,9 @@ static int ohci_hcd_tmio_drv_probe(struct platform_device *dev) if (!cell) return -EINVAL; + if (irq < 0) + return irq; + hcd = usb_create_hcd(&ohci_tmio_hc_driver, &dev->dev, dev_name(&dev->dev)); if (!hcd) { ret = -ENOMEM; -- cgit v1.2.3 From 0d45a1373e669880b8beaecc8765f44cb0241e47 Mon Sep 17 00:00:00 2001 From: Sergey Shtylyov Date: Fri, 13 Aug 2021 23:32:38 +0300 Subject: usb: phy: tahvo: add IRQ check The driver neglects to check the result of platform_get_irq()'s call and blithely passes the negative error codes to request_threaded_irq() (which takes *unsigned* IRQ #), causing it to fail with -EINVAL, overriding an original error code. Stop calling request_threaded_irq() with the invalid IRQ #s. Fixes: 9ba96ae5074c ("usb: omap1: Tahvo USB transceiver driver") Acked-by: Felipe Balbi Signed-off-by: Sergey Shtylyov Link: https://lore.kernel.org/r/8280d6a4-8e9a-7cfe-1aa9-db586dc9afdf@omp.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-tahvo.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/phy/phy-tahvo.c b/drivers/usb/phy/phy-tahvo.c index baebb1f5a973..a3e043e3e4aa 100644 --- a/drivers/usb/phy/phy-tahvo.c +++ b/drivers/usb/phy/phy-tahvo.c @@ -393,7 +393,9 @@ static int tahvo_usb_probe(struct platform_device *pdev) dev_set_drvdata(&pdev->dev, tu); - tu->irq = platform_get_irq(pdev, 0); + tu->irq = ret = platform_get_irq(pdev, 0); + if (ret < 0) + return ret; ret = request_threaded_irq(tu->irq, NULL, tahvo_usb_vbus_interrupt, IRQF_ONESHOT, "tahvo-vbus", tu); -- cgit v1.2.3 From 843714bb37d9a3780160d7b4a4a72b8077a77589 Mon Sep 17 00:00:00 2001 From: Jack Pham Date: Thu, 12 Aug 2021 01:26:35 -0700 Subject: usb: dwc3: Decouple USB 2.0 L1 & L2 events On DWC_usb3 revisions 3.00a and newer (including DWC_usb31 and DWC_usb32) the GUCTL1 register gained the DEV_DECOUPLE_L1L2_EVT field (bit 31) which when enabled allows the controller in device mode to treat USB 2.0 L1 LPM & L2 events separately. After commit d1d90dd27254 ("usb: dwc3: gadget: Enable suspend events") the controller will now receive events (and therefore interrupts) for every state change when entering/exiting either L1 or L2 states. Since L1 is handled entirely by the hardware and requires no software intervention, there is no need to even enable these events and unnecessarily notify the gadget driver. Enable the aforementioned bit to help reduce the overall interrupt count for these L1 events that don't need to be handled while retaining the events for full L2 suspend/wakeup. Tested-by: Jun Li Tested-by: Amit Pundir # for RB5 (sm8250) Tested-by: John Stultz # for HiKey960 & db845c Reviewed-by: Jun Li Acked-by: Felipe Balbi Signed-off-by: Jack Pham Link: https://lore.kernel.org/r/20210812082635.12924-1-jackp@codeaurora.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 9 +++++++++ drivers/usb/dwc3/core.h | 5 +++-- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index b194aecd2202..01866dcb953b 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1050,6 +1050,15 @@ static int dwc3_core_init(struct dwc3 *dwc) if (!DWC3_VER_IS_PRIOR(DWC3, 290A)) reg |= DWC3_GUCTL1_DEV_L1_EXIT_BY_HW; + /* + * Decouple USB 2.0 L1 & L2 events which will allow for + * gadget driver to only receive U3/L2 suspend & wakeup + * events and prevent the more frequent L1 LPM transitions + * from interrupting the driver. + */ + if (!DWC3_VER_IS_PRIOR(DWC3, 300A)) + reg |= DWC3_GUCTL1_DEV_DECOUPLE_L1L2_EVT; + if (dwc->dis_tx_ipgap_linecheck_quirk) reg |= DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS; diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index bcfeadc862b6..5612bfdf37da 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -256,9 +256,10 @@ #define DWC3_GUCTL_HSTINAUTORETRY BIT(14) /* Global User Control 1 Register */ -#define DWC3_GUCTL1_PARKMODE_DISABLE_SS BIT(17) +#define DWC3_GUCTL1_DEV_DECOUPLE_L1L2_EVT BIT(31) #define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS BIT(28) -#define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW BIT(24) +#define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW BIT(24) +#define DWC3_GUCTL1_PARKMODE_DISABLE_SS BIT(17) /* Global Status Register */ #define DWC3_GSTS_OTG_IP BIT(10) -- cgit v1.2.3 From b2582996a74799c84fd19473c7b914565249b050 Mon Sep 17 00:00:00 2001 From: Lukas Bulwahn Date: Wed, 18 Aug 2021 09:11:35 +0200 Subject: usb: host: remove dead EHCI support for on-chip PMC MSP71xx USB controller Commit 1b00767fd8e1 ("MIPS: Remove PMC MSP71xx platform") deletes ./arch/mips/pmcs-msp71xx/Kconfig, including its config MSP_HAS_USB. Hence, since then, the corresponding EHCI support for on-chip PMC MSP71xx USB controller is dead code. Remove this dead driver. Signed-off-by: Lukas Bulwahn Link: https://lore.kernel.org/r/20210818071137.22711-2-lukas.bulwahn@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 9 -- drivers/usb/host/ehci-hcd.c | 5 - drivers/usb/host/ehci-pmcmsp.c | 328 ----------------------------------------- 3 files changed, 342 deletions(-) delete mode 100644 drivers/usb/host/ehci-pmcmsp.c (limited to 'drivers') diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index df9428f1dc5e..c4736d1d020c 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -187,15 +187,6 @@ config USB_EHCI_PCI depends on USB_PCI default y -config USB_EHCI_HCD_PMC_MSP - tristate "EHCI support for on-chip PMC MSP71xx USB controller" - depends on MSP_HAS_USB - select USB_EHCI_BIG_ENDIAN_DESC - select USB_EHCI_BIG_ENDIAN_MMIO - help - Enables support for the onchip USB controller on the PMC_MSP7100 Family SoC's. - If unsure, say N. - config XPS_USB_HCD_XILINX bool "Use Xilinx usb host EHCI controller core" depends on (PPC32 || MICROBLAZE) diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 10b0365f3439..6bdc6d6bf74d 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -1296,11 +1296,6 @@ MODULE_LICENSE ("GPL"); #define XILINX_OF_PLATFORM_DRIVER ehci_hcd_xilinx_of_driver #endif -#ifdef CONFIG_USB_EHCI_HCD_PMC_MSP -#include "ehci-pmcmsp.c" -#define PLATFORM_DRIVER ehci_hcd_msp_driver -#endif - #ifdef CONFIG_SPARC_LEON #include "ehci-grlib.c" #define PLATFORM_DRIVER ehci_grlib_driver diff --git a/drivers/usb/host/ehci-pmcmsp.c b/drivers/usb/host/ehci-pmcmsp.c deleted file mode 100644 index 5fb92b956cc7..000000000000 --- a/drivers/usb/host/ehci-pmcmsp.c +++ /dev/null @@ -1,328 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * PMC MSP EHCI (Host Controller Driver) for USB. - * - * (C) Copyright 2006-2010 PMC-Sierra Inc - */ - -/* includes */ -#include -#include -#include -#include - -/* stream disable*/ -#define USB_CTRL_MODE_STREAM_DISABLE 0x10 - -/* threshold */ -#define USB_CTRL_FIFO_THRESH 0x00300000 - -/* register offset for usb_mode */ -#define USB_EHCI_REG_USB_MODE 0x68 - -/* register offset for usb fifo */ -#define USB_EHCI_REG_USB_FIFO 0x24 - -/* register offset for usb status */ -#define USB_EHCI_REG_USB_STATUS 0x44 - -/* serial/parallel transceiver */ -#define USB_EHCI_REG_BIT_STAT_STS (1<<29) - -/* TWI USB0 host device pin */ -#define MSP_PIN_USB0_HOST_DEV 49 - -/* TWI USB1 host device pin */ -#define MSP_PIN_USB1_HOST_DEV 50 - - -static void usb_hcd_tdi_set_mode(struct ehci_hcd *ehci) -{ - u8 *base; - u8 *statreg; - u8 *fiforeg; - u32 val; - struct ehci_regs *reg_base = ehci->regs; - - /* get register base */ - base = (u8 *)reg_base + USB_EHCI_REG_USB_MODE; - statreg = (u8 *)reg_base + USB_EHCI_REG_USB_STATUS; - fiforeg = (u8 *)reg_base + USB_EHCI_REG_USB_FIFO; - - /* Disable controller mode stream */ - val = ehci_readl(ehci, (u32 *)base); - ehci_writel(ehci, (val | USB_CTRL_MODE_STREAM_DISABLE), - (u32 *)base); - - /* clear STS to select parallel transceiver interface */ - val = ehci_readl(ehci, (u32 *)statreg); - val = val & ~USB_EHCI_REG_BIT_STAT_STS; - ehci_writel(ehci, val, (u32 *)statreg); - - /* write to set the proper fifo threshold */ - ehci_writel(ehci, USB_CTRL_FIFO_THRESH, (u32 *)fiforeg); - - /* set TWI GPIO USB_HOST_DEV pin high */ - gpio_direction_output(MSP_PIN_USB0_HOST_DEV, 1); -} - -/* called during probe() after chip reset completes */ -static int ehci_msp_setup(struct usb_hcd *hcd) -{ - struct ehci_hcd *ehci = hcd_to_ehci(hcd); - int retval; - - ehci->big_endian_mmio = 1; - ehci->big_endian_desc = 1; - - ehci->caps = hcd->regs; - hcd->has_tt = 1; - - retval = ehci_setup(hcd); - if (retval) - return retval; - - usb_hcd_tdi_set_mode(ehci); - - return retval; -} - - -/* configure so an HC device and id are always provided - * always called with process context; sleeping is OK - */ - -static int usb_hcd_msp_map_regs(struct mspusb_device *dev) -{ - struct resource *res; - struct platform_device *pdev = &dev->dev; - u32 res_len; - int retval; - - /* MAB register space */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - if (res == NULL) - return -ENOMEM; - res_len = resource_size(res); - if (!request_mem_region(res->start, res_len, "mab regs")) - return -EBUSY; - - dev->mab_regs = ioremap(res->start, res_len); - if (dev->mab_regs == NULL) { - retval = -ENOMEM; - goto err1; - } - - /* MSP USB register space */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 2); - if (res == NULL) { - retval = -ENOMEM; - goto err2; - } - res_len = resource_size(res); - if (!request_mem_region(res->start, res_len, "usbid regs")) { - retval = -EBUSY; - goto err2; - } - dev->usbid_regs = ioremap(res->start, res_len); - if (dev->usbid_regs == NULL) { - retval = -ENOMEM; - goto err3; - } - - return 0; -err3: - res = platform_get_resource(pdev, IORESOURCE_MEM, 2); - res_len = resource_size(res); - release_mem_region(res->start, res_len); -err2: - iounmap(dev->mab_regs); -err1: - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - res_len = resource_size(res); - release_mem_region(res->start, res_len); - dev_err(&pdev->dev, "Failed to map non-EHCI regs.\n"); - return retval; -} - -/** - * usb_hcd_msp_probe - initialize PMC MSP-based HCDs - * @driver: Pointer to hc driver instance - * @dev: USB controller to probe - * - * Context: task context, might sleep - * - * Allocates basic resources for this USB host controller, and - * then invokes the start() method for the HCD associated with it - * through the hotplug entry's driver_data. - */ -int usb_hcd_msp_probe(const struct hc_driver *driver, - struct platform_device *dev) -{ - int retval; - struct usb_hcd *hcd; - struct resource *res; - struct ehci_hcd *ehci ; - - hcd = usb_create_hcd(driver, &dev->dev, "pmcmsp"); - if (!hcd) - return -ENOMEM; - - res = platform_get_resource(dev, IORESOURCE_MEM, 0); - if (res == NULL) { - pr_debug("No IOMEM resource info for %s.\n", dev->name); - retval = -ENOMEM; - goto err1; - } - hcd->rsrc_start = res->start; - hcd->rsrc_len = resource_size(res); - if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, dev->name)) { - retval = -EBUSY; - goto err1; - } - hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); - if (!hcd->regs) { - pr_debug("ioremap failed"); - retval = -ENOMEM; - goto err2; - } - - res = platform_get_resource(dev, IORESOURCE_IRQ, 0); - if (res == NULL) { - dev_err(&dev->dev, "No IRQ resource info for %s.\n", dev->name); - retval = -ENOMEM; - goto err3; - } - - /* Map non-EHCI register spaces */ - retval = usb_hcd_msp_map_regs(to_mspusb_device(dev)); - if (retval != 0) - goto err3; - - ehci = hcd_to_ehci(hcd); - ehci->big_endian_mmio = 1; - ehci->big_endian_desc = 1; - - - retval = usb_add_hcd(hcd, res->start, IRQF_SHARED); - if (retval == 0) { - device_wakeup_enable(hcd->self.controller); - return 0; - } - - usb_remove_hcd(hcd); -err3: - iounmap(hcd->regs); -err2: - release_mem_region(hcd->rsrc_start, hcd->rsrc_len); -err1: - usb_put_hcd(hcd); - - return retval; -} - - - -/** - * usb_hcd_msp_remove - shutdown processing for PMC MSP-based HCDs - * @hcd: USB Host Controller being removed - * - * Context: task context, might sleep - * - * Reverses the effect of usb_hcd_msp_probe(), first invoking - * the HCD's stop() method. It is always called from a thread - * context, normally "rmmod", "apmd", or something similar. - * - * may be called without controller electrically present - * may be called with controller, bus, and devices active - */ -static void usb_hcd_msp_remove(struct usb_hcd *hcd) -{ - usb_remove_hcd(hcd); - iounmap(hcd->regs); - release_mem_region(hcd->rsrc_start, hcd->rsrc_len); - usb_put_hcd(hcd); -} - -static const struct hc_driver ehci_msp_hc_driver = { - .description = hcd_name, - .product_desc = "PMC MSP EHCI", - .hcd_priv_size = sizeof(struct ehci_hcd), - - /* - * generic hardware linkage - */ - .irq = ehci_irq, - .flags = HCD_MEMORY | HCD_DMA | HCD_USB2 | HCD_BH, - - /* - * basic lifecycle operations - */ - .reset = ehci_msp_setup, - .shutdown = ehci_shutdown, - .start = ehci_run, - .stop = ehci_stop, - - /* - * managing i/o requests and associated device resources - */ - .urb_enqueue = ehci_urb_enqueue, - .urb_dequeue = ehci_urb_dequeue, - .endpoint_disable = ehci_endpoint_disable, - .endpoint_reset = ehci_endpoint_reset, - - /* - * scheduling support - */ - .get_frame_number = ehci_get_frame, - - /* - * root hub support - */ - .hub_status_data = ehci_hub_status_data, - .hub_control = ehci_hub_control, - .bus_suspend = ehci_bus_suspend, - .bus_resume = ehci_bus_resume, - .relinquish_port = ehci_relinquish_port, - .port_handed_over = ehci_port_handed_over, - - .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, -}; - -static int ehci_hcd_msp_drv_probe(struct platform_device *pdev) -{ - int ret; - - pr_debug("In ehci_hcd_msp_drv_probe"); - - if (usb_disabled()) - return -ENODEV; - - gpio_request(MSP_PIN_USB0_HOST_DEV, "USB0_HOST_DEV_GPIO"); - - ret = usb_hcd_msp_probe(&ehci_msp_hc_driver, pdev); - - return ret; -} - -static int ehci_hcd_msp_drv_remove(struct platform_device *pdev) -{ - struct usb_hcd *hcd = platform_get_drvdata(pdev); - - usb_hcd_msp_remove(hcd); - - /* free TWI GPIO USB_HOST_DEV pin */ - gpio_free(MSP_PIN_USB0_HOST_DEV); - - return 0; -} - -MODULE_ALIAS("pmcmsp-ehci"); - -static struct platform_driver ehci_hcd_msp_driver = { - .probe = ehci_hcd_msp_drv_probe, - .remove = ehci_hcd_msp_drv_remove, - .driver = { - .name = "pmcmsp-ehci", - }, -}; -- cgit v1.2.3 From 3b445c99c756d771cb59840ca3ce46cb15c8b299 Mon Sep 17 00:00:00 2001 From: Lukas Bulwahn Date: Wed, 18 Aug 2021 09:11:36 +0200 Subject: usb: host: remove line for obsolete config USB_HWA_HCD Commit 71ed79b0e4be ("USB: Move wusbcore and UWB to staging as it is obsolete") misses to adjust some part in ./drivers/usb/host/Makefile. Hence, ./scripts/checkkconfigsymbols.py warns: USB_HWA_HCD Referencing files: drivers/usb/Makefile Remove the missing piece of this code removal. Signed-off-by: Lukas Bulwahn Link: https://lore.kernel.org/r/20210818071137.22711-3-lukas.bulwahn@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/Makefile | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 3e2cc95b7b0b..643edf5fe18c 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -31,7 +31,6 @@ obj-$(CONFIG_USB_SL811_HCD) += host/ obj-$(CONFIG_USB_ISP1362_HCD) += host/ obj-$(CONFIG_USB_U132_HCD) += host/ obj-$(CONFIG_USB_R8A66597_HCD) += host/ -obj-$(CONFIG_USB_HWA_HCD) += host/ obj-$(CONFIG_USB_FSL_USB2) += host/ obj-$(CONFIG_USB_FOTG210_HCD) += host/ obj-$(CONFIG_USB_MAX3421_HCD) += host/ -- cgit v1.2.3 From e77939ee63a7faf5def34c97ee0137f234367459 Mon Sep 17 00:00:00 2001 From: Lukas Bulwahn Date: Wed, 18 Aug 2021 09:11:37 +0200 Subject: usb: remove reference to deleted config STB03xxx Commit 7583b63c343c ("powerpc/40x: Remove STB03xxx") removes the config STB03xxx, but left a reference in ./drivers/usb/Kconfig behind. Hence, ./scripts/checkkconfigsymbols.py warns: STB03xxx Referencing files: drivers/usb/Kconfig Remove this reference to the deleted config. Signed-off-by: Lukas Bulwahn Link: https://lore.kernel.org/r/20210818071137.22711-4-lukas.bulwahn@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 26475b409b53..578a439e71b5 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -11,7 +11,7 @@ config USB_OHCI_BIG_ENDIAN_MMIO config USB_OHCI_LITTLE_ENDIAN bool - default n if STB03xxx || PPC_MPC52xx + default n if PPC_MPC52xx default y config USB_EHCI_BIG_ENDIAN_MMIO -- cgit v1.2.3 From 1bc220835526ae076eecfb7ed513f80f22cf840d Mon Sep 17 00:00:00 2001 From: Pavel Hofman Date: Tue, 17 Aug 2021 12:05:55 +0200 Subject: usb: gadget: f_uac1: fixing inconsistent indenting Fixing inconsistent indenting identified by kernel test robot. Signed-off-by: Pavel Hofman Acked-By: Felipe Balbi Link: https://lore.kernel.org/r/20210817100555.4437-1-pavel.hofman@ivitera.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_uac1.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c index 3b3db1a8df75..5b3502df4e13 100644 --- a/drivers/usb/gadget/function/f_uac1.c +++ b/drivers/usb/gadget/function/f_uac1.c @@ -1084,24 +1084,24 @@ static int f_audio_validate_opts(struct g_audio *audio, struct device *dev) if (opts->p_volume_max <= opts->p_volume_min) { dev_err(dev, "Error: incorrect playback volume max/min\n"); - return -EINVAL; + return -EINVAL; } else if (opts->c_volume_max <= opts->c_volume_min) { dev_err(dev, "Error: incorrect capture volume max/min\n"); - return -EINVAL; + return -EINVAL; } else if (opts->p_volume_res <= 0) { dev_err(dev, "Error: negative/zero playback volume resolution\n"); - return -EINVAL; + return -EINVAL; } else if (opts->c_volume_res <= 0) { dev_err(dev, "Error: negative/zero capture volume resolution\n"); - return -EINVAL; + return -EINVAL; } if ((opts->p_volume_max - opts->p_volume_min) % opts->p_volume_res) { dev_err(dev, "Error: incorrect playback volume resolution\n"); - return -EINVAL; + return -EINVAL; } else if ((opts->c_volume_max - opts->c_volume_min) % opts->c_volume_res) { dev_err(dev, "Error: incorrect capture volume resolution\n"); - return -EINVAL; + return -EINVAL; } return 0; -- cgit v1.2.3 From 2af0c5ffadaf9d13eca28409d4238b4e672942d3 Mon Sep 17 00:00:00 2001 From: Nadezda Lutovinova Date: Wed, 18 Aug 2021 17:12:47 +0300 Subject: usb: gadget: mv_u3d: request_irq() after initializing UDC If IRQ occurs between calling request_irq() and mv_u3d_eps_init(), then null pointer dereference occurs since u3d->eps[] wasn't initialized yet but used in mv_u3d_nuke(). The patch puts registration of the interrupt handler after initializing of neccesery data. Found by Linux Driver Verification project (linuxtesting.org). Fixes: 90fccb529d24 ("usb: gadget: Gadget directory cleanup - group UDC drivers") Acked-by: Felipe Balbi Signed-off-by: Nadezda Lutovinova Link: https://lore.kernel.org/r/20210818141247.4794-1-lutovinova@ispras.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/mv_u3d_core.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/mv_u3d_core.c b/drivers/usb/gadget/udc/mv_u3d_core.c index ce3d7a3eb7e3..a1057ddfbda3 100644 --- a/drivers/usb/gadget/udc/mv_u3d_core.c +++ b/drivers/usb/gadget/udc/mv_u3d_core.c @@ -1921,14 +1921,6 @@ static int mv_u3d_probe(struct platform_device *dev) goto err_get_irq; } u3d->irq = r->start; - if (request_irq(u3d->irq, mv_u3d_irq, - IRQF_SHARED, driver_name, u3d)) { - u3d->irq = 0; - dev_err(&dev->dev, "Request irq %d for u3d failed\n", - u3d->irq); - retval = -ENODEV; - goto err_request_irq; - } /* initialize gadget structure */ u3d->gadget.ops = &mv_u3d_ops; /* usb_gadget_ops */ @@ -1941,6 +1933,15 @@ static int mv_u3d_probe(struct platform_device *dev) mv_u3d_eps_init(u3d); + if (request_irq(u3d->irq, mv_u3d_irq, + IRQF_SHARED, driver_name, u3d)) { + u3d->irq = 0; + dev_err(&dev->dev, "Request irq %d for u3d failed\n", + u3d->irq); + retval = -ENODEV; + goto err_request_irq; + } + /* external vbus detection */ if (u3d->vbus) { u3d->clock_gating = 1; @@ -1964,8 +1965,8 @@ static int mv_u3d_probe(struct platform_device *dev) err_unregister: free_irq(u3d->irq, u3d); -err_request_irq: err_get_irq: +err_request_irq: kfree(u3d->status_req); err_alloc_status_req: kfree(u3d->eps); -- cgit v1.2.3 From e4788edc730a0d2b26e1ae1f08fbb3f635b92dbb Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 18 Aug 2021 10:30:18 -0700 Subject: USB: EHCI: Add alias for Broadcom INSNREG Refactor struct ehci_regs to avoid accessing beyond the end of port_status. This change results in no difference in the final object code. Avoids several warnings when building with -Warray-bounds: drivers/usb/host/ehci-brcm.c: In function 'ehci_brcm_reset': drivers/usb/host/ehci-brcm.c:113:32: warning: array subscript 16 is above array bounds of 'u32[15]' {aka 'unsigned int[15]'} [-Warray-bounds] 113 | ehci_writel(ehci, 0x00800040, &ehci->regs->port_status[0x10]); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In file included from drivers/usb/host/ehci.h:274, from drivers/usb/host/ehci-brcm.c:15: ./include/linux/usb/ehci_def.h:132:7: note: while referencing 'port_status' 132 | u32 port_status[HCS_N_PORTS_MAX]; | ^~~~~~~~~~~ Note that the documentation around this proprietary register was confusing. If "USB_EHCI_INSNREG00" is at port_status[0x0f], its offset would be 0x80 (not 0x90). The comments have been adjusted to fix this apparent typo. Fixes: 9df231511bd6 ("usb: ehci: Add new EHCI driver for Broadcom STB SoC's") Cc: Al Cooper Cc: Alan Stern Cc: Greg Kroah-Hartman Cc: linux-usb@vger.kernel.org Cc: bcm-kernel-feedback-list@broadcom.com Suggested-by: Arnd Bergmann Acked-by: Alan Stern Signed-off-by: Kees Cook Link: https://lore.kernel.org/r/20210818173018.2259231-3-keescook@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-brcm.c | 11 ++++------- include/linux/usb/ehci_def.h | 13 ++++++++++--- 2 files changed, 14 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-brcm.c b/drivers/usb/host/ehci-brcm.c index 3e0ebe8cc649..d3626bfa966b 100644 --- a/drivers/usb/host/ehci-brcm.c +++ b/drivers/usb/host/ehci-brcm.c @@ -108,10 +108,9 @@ static int ehci_brcm_reset(struct usb_hcd *hcd) /* * SWLINUX-1705: Avoid OUT packet underflows during high memory * bus usage - * port_status[0x0f] = Broadcom-proprietary USB_EHCI_INSNREG00 @ 0x90 */ - ehci_writel(ehci, 0x00800040, &ehci->regs->port_status[0x10]); - ehci_writel(ehci, 0x00000001, &ehci->regs->port_status[0x12]); + ehci_writel(ehci, 0x00800040, &ehci->regs->brcm_insnreg[1]); + ehci_writel(ehci, 0x00000001, &ehci->regs->brcm_insnreg[3]); return ehci_setup(hcd); } @@ -223,11 +222,9 @@ static int __maybe_unused ehci_brcm_resume(struct device *dev) /* * SWLINUX-1705: Avoid OUT packet underflows during high memory * bus usage - * port_status[0x0f] = Broadcom-proprietary USB_EHCI_INSNREG00 - * @ 0x90 */ - ehci_writel(ehci, 0x00800040, &ehci->regs->port_status[0x10]); - ehci_writel(ehci, 0x00000001, &ehci->regs->port_status[0x12]); + ehci_writel(ehci, 0x00800040, &ehci->regs->brcm_insnreg[1]); + ehci_writel(ehci, 0x00000001, &ehci->regs->brcm_insnreg[3]); ehci_resume(hcd, false); diff --git a/include/linux/usb/ehci_def.h b/include/linux/usb/ehci_def.h index dcbe2b068569..c892c5bc6638 100644 --- a/include/linux/usb/ehci_def.h +++ b/include/linux/usb/ehci_def.h @@ -176,16 +176,23 @@ struct ehci_regs { #define USBMODE_CM_HC (3<<0) /* host controller mode */ #define USBMODE_CM_IDLE (0<<0) /* idle state */ }; - u32 reserved4; /* Moorestown has some non-standard registers, partially due to the fact that * its EHCI controller has both TT and LPM support. HOSTPCx are extensions to * PORTSCx */ - /* HOSTPC: offset 0x84 */ - u32 hostpc[HCS_N_PORTS_MAX]; + union { + struct { + u32 reserved4; + /* HOSTPC: offset 0x84 */ + u32 hostpc[HCS_N_PORTS_MAX]; #define HOSTPC_PHCD (1<<22) /* Phy clock disable */ #define HOSTPC_PSPD (3<<25) /* Port speed detection */ + }; + + /* Broadcom-proprietary USB_EHCI_INSNREG00 @ 0x80 */ + u32 brcm_insnreg[4]; + }; u32 reserved5[2]; -- cgit v1.2.3 From e5d6a7c6cfae9e714a0e8ff64facd1ac68a784c6 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Fri, 18 Jun 2021 16:28:58 +0800 Subject: usb: chipidea: host: fix port index underflow and UBSAN complains If wIndex is 0 (and it often is), these calculations underflow and UBSAN complains, here resolve this by not decrementing the index when it is equal to 0, this copies the solution from commit 85e3990bea49 ("USB: EHCI: avoid undefined pointer arithmetic and placate UBSAN") Reported-by: Zhipeng Wang Signed-off-by: Li Jun Link: https://lore.kernel.org/r/1624004938-2399-1-git-send-email-jun.li@nxp.com Signed-off-by: Peter Chen --- drivers/usb/chipidea/host.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index e86d13c04bdb..bdc3885c0d49 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -240,15 +240,18 @@ static int ci_ehci_hub_control( ) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); + unsigned int ports = HCS_N_PORTS(ehci->hcs_params); u32 __iomem *status_reg; - u32 temp; + u32 temp, port_index; unsigned long flags; int retval = 0; bool done = false; struct device *dev = hcd->self.controller; struct ci_hdrc *ci = dev_get_drvdata(dev); - status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; + port_index = wIndex & 0xff; + port_index -= (port_index > 0); + status_reg = &ehci->regs->port_status[port_index]; spin_lock_irqsave(&ehci->lock, flags); @@ -260,6 +263,11 @@ static int ci_ehci_hub_control( } if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) { + if (!wIndex || wIndex > ports) { + retval = -EPIPE; + goto done; + } + temp = ehci_readl(ehci, status_reg); if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) { retval = -EPIPE; @@ -288,7 +296,7 @@ static int ci_ehci_hub_control( ehci_writel(ehci, temp, status_reg); } - set_bit((wIndex & 0xff) - 1, &ehci->suspended_ports); + set_bit(port_index, &ehci->suspended_ports); goto done; } -- cgit v1.2.3 From d7428bc26fc767942c38d74b80299bcd4f01e7cb Mon Sep 17 00:00:00 2001 From: Maxim Devaev Date: Sat, 21 Aug 2021 16:40:04 +0300 Subject: usb: gadget: f_hid: optional SETUP/SET_REPORT mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit f_hid provides the OUT Endpoint as only way for receiving reports from the host. SETUP/SET_REPORT method is not supported, and this causes a number of compatibility problems with various host drivers, especially in the case of keyboard emulation using f_hid. - Some hosts do not support the OUT Endpoint and ignore it, so it becomes impossible for the gadget to receive a report from the host. In the case of a keyboard, the gadget loses the ability to receive the status of the LEDs. - Some BIOSes/UEFIs can't work with HID devices with the OUT Endpoint at all. This may be due to their bugs or incomplete implementation of the HID standard. For example, absolutely all Apple UEFIs can't handle the OUT Endpoint if it goes after IN Endpoint in the descriptor and require the reverse order (OUT, IN) which is a violation of the standard. Other hosts either do not initialize gadgets with a descriptor containing the OUT Endpoint completely (like some HP and DELL BIOSes and embedded firmwares like on KVM switches), or initialize them, but will not poll the IN Endpoint. This patch adds configfs option no_out_endpoint=1 to disable the OUT Endpoint and allows f_hid to receive reports from the host via SETUP/SET_REPORT. Previously, there was such a feature in f_hid, but it was replaced by the OUT Endpoint [1] in the commit 99c515005857 ("usb: gadget: hidg: register OUT INT endpoint for SET_REPORT"). So this patch actually returns the removed functionality while making it optional. For backward compatibility reasons, the OUT Endpoint mode remains the default behaviour. - The OUT Endpoint mode provides the report queue and reduces USB overhead (eliminating SETUP routine) on transmitting a report from the host. - If the SETUP/SET_REPORT mode is used, there is no report queue, so the userspace will only read last report. For classic HID devices like keyboards this is not a problem, since it's intended to transmit the status of the LEDs and only the last report is important. This mode provides better compatibility with strange and buggy host drivers. Both modes passed USBCV tests. Checking with the USB protocol analyzer also confirmed that everything is working as it should and the new mode ensures operability in all of the described cases. Link: https://www.spinics.net/lists/linux-usb/msg65494.html [1] Reviewed-by: Maciej Żenczykowski Acked-by: Felipe Balbi Signed-off-by: Maxim Devaev Link: https://lore.kernel.org/r/20210821134004.363217-1-mdevaev@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_hid.c | 220 ++++++++++++++++++++++++++++++------ drivers/usb/gadget/function/u_hid.h | 1 + 2 files changed, 188 insertions(+), 33 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c index bb476e121eae..ca0a7d9eaa34 100644 --- a/drivers/usb/gadget/function/f_hid.c +++ b/drivers/usb/gadget/function/f_hid.c @@ -45,12 +45,25 @@ struct f_hidg { unsigned short report_desc_length; char *report_desc; unsigned short report_length; + /* + * use_out_ep - if true, the OUT Endpoint (interrupt out method) + * will be used to receive reports from the host + * using functions with the "intout" suffix. + * Otherwise, the OUT Endpoint will not be configured + * and the SETUP/SET_REPORT method ("ssreport" suffix) + * will be used to receive reports. + */ + bool use_out_ep; /* recv report */ - struct list_head completed_out_req; spinlock_t read_spinlock; wait_queue_head_t read_queue; + /* recv report - interrupt out only (use_out_ep == 1) */ + struct list_head completed_out_req; unsigned int qlen; + /* recv report - setup set_report only (use_out_ep == 0) */ + char *set_report_buf; + unsigned int set_report_length; /* send report */ spinlock_t write_spinlock; @@ -79,7 +92,7 @@ static struct usb_interface_descriptor hidg_interface_desc = { .bDescriptorType = USB_DT_INTERFACE, /* .bInterfaceNumber = DYNAMIC */ .bAlternateSetting = 0, - .bNumEndpoints = 2, + /* .bNumEndpoints = DYNAMIC (depends on use_out_ep) */ .bInterfaceClass = USB_CLASS_HID, /* .bInterfaceSubClass = DYNAMIC */ /* .bInterfaceProtocol = DYNAMIC */ @@ -140,7 +153,7 @@ static struct usb_ss_ep_comp_descriptor hidg_ss_out_comp_desc = { /* .wBytesPerInterval = DYNAMIC */ }; -static struct usb_descriptor_header *hidg_ss_descriptors[] = { +static struct usb_descriptor_header *hidg_ss_descriptors_intout[] = { (struct usb_descriptor_header *)&hidg_interface_desc, (struct usb_descriptor_header *)&hidg_desc, (struct usb_descriptor_header *)&hidg_ss_in_ep_desc, @@ -150,6 +163,14 @@ static struct usb_descriptor_header *hidg_ss_descriptors[] = { NULL, }; +static struct usb_descriptor_header *hidg_ss_descriptors_ssreport[] = { + (struct usb_descriptor_header *)&hidg_interface_desc, + (struct usb_descriptor_header *)&hidg_desc, + (struct usb_descriptor_header *)&hidg_ss_in_ep_desc, + (struct usb_descriptor_header *)&hidg_ss_in_comp_desc, + NULL, +}; + /* High-Speed Support */ static struct usb_endpoint_descriptor hidg_hs_in_ep_desc = { @@ -176,7 +197,7 @@ static struct usb_endpoint_descriptor hidg_hs_out_ep_desc = { */ }; -static struct usb_descriptor_header *hidg_hs_descriptors[] = { +static struct usb_descriptor_header *hidg_hs_descriptors_intout[] = { (struct usb_descriptor_header *)&hidg_interface_desc, (struct usb_descriptor_header *)&hidg_desc, (struct usb_descriptor_header *)&hidg_hs_in_ep_desc, @@ -184,6 +205,13 @@ static struct usb_descriptor_header *hidg_hs_descriptors[] = { NULL, }; +static struct usb_descriptor_header *hidg_hs_descriptors_ssreport[] = { + (struct usb_descriptor_header *)&hidg_interface_desc, + (struct usb_descriptor_header *)&hidg_desc, + (struct usb_descriptor_header *)&hidg_hs_in_ep_desc, + NULL, +}; + /* Full-Speed Support */ static struct usb_endpoint_descriptor hidg_fs_in_ep_desc = { @@ -210,7 +238,7 @@ static struct usb_endpoint_descriptor hidg_fs_out_ep_desc = { */ }; -static struct usb_descriptor_header *hidg_fs_descriptors[] = { +static struct usb_descriptor_header *hidg_fs_descriptors_intout[] = { (struct usb_descriptor_header *)&hidg_interface_desc, (struct usb_descriptor_header *)&hidg_desc, (struct usb_descriptor_header *)&hidg_fs_in_ep_desc, @@ -218,6 +246,13 @@ static struct usb_descriptor_header *hidg_fs_descriptors[] = { NULL, }; +static struct usb_descriptor_header *hidg_fs_descriptors_ssreport[] = { + (struct usb_descriptor_header *)&hidg_interface_desc, + (struct usb_descriptor_header *)&hidg_desc, + (struct usb_descriptor_header *)&hidg_fs_in_ep_desc, + NULL, +}; + /*-------------------------------------------------------------------------*/ /* Strings */ @@ -241,8 +276,8 @@ static struct usb_gadget_strings *ct_func_strings[] = { /*-------------------------------------------------------------------------*/ /* Char Device */ -static ssize_t f_hidg_read(struct file *file, char __user *buffer, - size_t count, loff_t *ptr) +static ssize_t f_hidg_intout_read(struct file *file, char __user *buffer, + size_t count, loff_t *ptr) { struct f_hidg *hidg = file->private_data; struct f_hidg_req_list *list; @@ -255,15 +290,15 @@ static ssize_t f_hidg_read(struct file *file, char __user *buffer, spin_lock_irqsave(&hidg->read_spinlock, flags); -#define READ_COND (!list_empty(&hidg->completed_out_req)) +#define READ_COND_INTOUT (!list_empty(&hidg->completed_out_req)) /* wait for at least one buffer to complete */ - while (!READ_COND) { + while (!READ_COND_INTOUT) { spin_unlock_irqrestore(&hidg->read_spinlock, flags); if (file->f_flags & O_NONBLOCK) return -EAGAIN; - if (wait_event_interruptible(hidg->read_queue, READ_COND)) + if (wait_event_interruptible(hidg->read_queue, READ_COND_INTOUT)) return -ERESTARTSYS; spin_lock_irqsave(&hidg->read_spinlock, flags); @@ -313,6 +348,60 @@ static ssize_t f_hidg_read(struct file *file, char __user *buffer, return count; } +#define READ_COND_SSREPORT (hidg->set_report_buf != NULL) + +static ssize_t f_hidg_ssreport_read(struct file *file, char __user *buffer, + size_t count, loff_t *ptr) +{ + struct f_hidg *hidg = file->private_data; + char *tmp_buf = NULL; + unsigned long flags; + + if (!count) + return 0; + + spin_lock_irqsave(&hidg->read_spinlock, flags); + + while (!READ_COND_SSREPORT) { + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (wait_event_interruptible(hidg->read_queue, READ_COND_SSREPORT)) + return -ERESTARTSYS; + + spin_lock_irqsave(&hidg->read_spinlock, flags); + } + + count = min_t(unsigned int, count, hidg->set_report_length); + tmp_buf = hidg->set_report_buf; + hidg->set_report_buf = NULL; + + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + + if (tmp_buf != NULL) { + count -= copy_to_user(buffer, tmp_buf, count); + kfree(tmp_buf); + } else { + count = -ENOMEM; + } + + wake_up(&hidg->read_queue); + + return count; +} + +static ssize_t f_hidg_read(struct file *file, char __user *buffer, + size_t count, loff_t *ptr) +{ + struct f_hidg *hidg = file->private_data; + + if (hidg->use_out_ep) + return f_hidg_intout_read(file, buffer, count, ptr); + else + return f_hidg_ssreport_read(file, buffer, count, ptr); +} + static void f_hidg_req_complete(struct usb_ep *ep, struct usb_request *req) { struct f_hidg *hidg = (struct f_hidg *)ep->driver_data; @@ -433,14 +522,20 @@ static __poll_t f_hidg_poll(struct file *file, poll_table *wait) if (WRITE_COND) ret |= EPOLLOUT | EPOLLWRNORM; - if (READ_COND) - ret |= EPOLLIN | EPOLLRDNORM; + if (hidg->use_out_ep) { + if (READ_COND_INTOUT) + ret |= EPOLLIN | EPOLLRDNORM; + } else { + if (READ_COND_SSREPORT) + ret |= EPOLLIN | EPOLLRDNORM; + } return ret; } #undef WRITE_COND -#undef READ_COND +#undef READ_COND_SSREPORT +#undef READ_COND_INTOUT static int f_hidg_release(struct inode *inode, struct file *fd) { @@ -467,7 +562,7 @@ static inline struct usb_request *hidg_alloc_ep_req(struct usb_ep *ep, return alloc_ep_req(ep, length); } -static void hidg_set_report_complete(struct usb_ep *ep, struct usb_request *req) +static void hidg_intout_complete(struct usb_ep *ep, struct usb_request *req) { struct f_hidg *hidg = (struct f_hidg *) req->context; struct usb_composite_dev *cdev = hidg->func.config->cdev; @@ -502,6 +597,37 @@ free_req: } } +static void hidg_ssreport_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_hidg *hidg = (struct f_hidg *)req->context; + struct usb_composite_dev *cdev = hidg->func.config->cdev; + char *new_buf = NULL; + unsigned long flags; + + if (req->status != 0 || req->buf == NULL || req->actual == 0) { + ERROR(cdev, + "%s FAILED: status=%d, buf=%p, actual=%d\n", + __func__, req->status, req->buf, req->actual); + return; + } + + spin_lock_irqsave(&hidg->read_spinlock, flags); + + new_buf = krealloc(hidg->set_report_buf, req->actual, GFP_ATOMIC); + if (new_buf == NULL) { + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + return; + } + hidg->set_report_buf = new_buf; + + hidg->set_report_length = req->actual; + memcpy(hidg->set_report_buf, req->buf, req->actual); + + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + + wake_up(&hidg->read_queue); +} + static int hidg_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) { @@ -549,7 +675,11 @@ static int hidg_setup(struct usb_function *f, case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 | HID_REQ_SET_REPORT): VDBG(cdev, "set_report | wLength=%d\n", ctrl->wLength); - goto stall; + if (hidg->use_out_ep) + goto stall; + req->complete = hidg_ssreport_complete; + req->context = hidg; + goto respond; break; case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 @@ -637,15 +767,18 @@ static void hidg_disable(struct usb_function *f) unsigned long flags; usb_ep_disable(hidg->in_ep); - usb_ep_disable(hidg->out_ep); - spin_lock_irqsave(&hidg->read_spinlock, flags); - list_for_each_entry_safe(list, next, &hidg->completed_out_req, list) { - free_ep_req(hidg->out_ep, list->req); - list_del(&list->list); - kfree(list); + if (hidg->out_ep) { + usb_ep_disable(hidg->out_ep); + + spin_lock_irqsave(&hidg->read_spinlock, flags); + list_for_each_entry_safe(list, next, &hidg->completed_out_req, list) { + free_ep_req(hidg->out_ep, list->req); + list_del(&list->list); + kfree(list); + } + spin_unlock_irqrestore(&hidg->read_spinlock, flags); } - spin_unlock_irqrestore(&hidg->read_spinlock, flags); spin_lock_irqsave(&hidg->write_spinlock, flags); if (!hidg->write_pending) { @@ -691,8 +824,7 @@ static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) } } - - if (hidg->out_ep != NULL) { + if (hidg->use_out_ep && hidg->out_ep != NULL) { /* restart endpoint */ usb_ep_disable(hidg->out_ep); @@ -717,7 +849,7 @@ static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) hidg_alloc_ep_req(hidg->out_ep, hidg->report_length); if (req) { - req->complete = hidg_set_report_complete; + req->complete = hidg_intout_complete; req->context = hidg; status = usb_ep_queue(hidg->out_ep, req, GFP_ATOMIC); @@ -743,7 +875,8 @@ static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) } return 0; disable_out_ep: - usb_ep_disable(hidg->out_ep); + if (hidg->out_ep) + usb_ep_disable(hidg->out_ep); free_req_in: if (req_in) free_ep_req(hidg->in_ep, req_in); @@ -795,14 +928,21 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) goto fail; hidg->in_ep = ep; - ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_out_ep_desc); - if (!ep) - goto fail; - hidg->out_ep = ep; + hidg->out_ep = NULL; + if (hidg->use_out_ep) { + ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_out_ep_desc); + if (!ep) + goto fail; + hidg->out_ep = ep; + } + + /* used only if use_out_ep == 1 */ + hidg->set_report_buf = NULL; /* set descriptor dynamic values */ hidg_interface_desc.bInterfaceSubClass = hidg->bInterfaceSubClass; hidg_interface_desc.bInterfaceProtocol = hidg->bInterfaceProtocol; + hidg_interface_desc.bNumEndpoints = hidg->use_out_ep ? 2 : 1; hidg->protocol = HID_REPORT_PROTOCOL; hidg->idle = 1; hidg_ss_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); @@ -833,9 +973,19 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) hidg_ss_out_ep_desc.bEndpointAddress = hidg_fs_out_ep_desc.bEndpointAddress; - status = usb_assign_descriptors(f, hidg_fs_descriptors, - hidg_hs_descriptors, hidg_ss_descriptors, - hidg_ss_descriptors); + if (hidg->use_out_ep) + status = usb_assign_descriptors(f, + hidg_fs_descriptors_intout, + hidg_hs_descriptors_intout, + hidg_ss_descriptors_intout, + hidg_ss_descriptors_intout); + else + status = usb_assign_descriptors(f, + hidg_fs_descriptors_ssreport, + hidg_hs_descriptors_ssreport, + hidg_ss_descriptors_ssreport, + hidg_ss_descriptors_ssreport); + if (status) goto fail; @@ -950,6 +1100,7 @@ CONFIGFS_ATTR(f_hid_opts_, name) F_HID_OPT(subclass, 8, 255); F_HID_OPT(protocol, 8, 255); +F_HID_OPT(no_out_endpoint, 8, 1); F_HID_OPT(report_length, 16, 65535); static ssize_t f_hid_opts_report_desc_show(struct config_item *item, char *page) @@ -1009,6 +1160,7 @@ CONFIGFS_ATTR_RO(f_hid_opts_, dev); static struct configfs_attribute *hid_attrs[] = { &f_hid_opts_attr_subclass, &f_hid_opts_attr_protocol, + &f_hid_opts_attr_no_out_endpoint, &f_hid_opts_attr_report_length, &f_hid_opts_attr_report_desc, &f_hid_opts_attr_dev, @@ -1093,6 +1245,7 @@ static void hidg_free(struct usb_function *f) hidg = func_to_hidg(f); opts = container_of(f->fi, struct f_hid_opts, func_inst); kfree(hidg->report_desc); + kfree(hidg->set_report_buf); kfree(hidg); mutex_lock(&opts->lock); --opts->refcnt; @@ -1139,6 +1292,7 @@ static struct usb_function *hidg_alloc(struct usb_function_instance *fi) return ERR_PTR(-ENOMEM); } } + hidg->use_out_ep = !opts->no_out_endpoint; mutex_unlock(&opts->lock); diff --git a/drivers/usb/gadget/function/u_hid.h b/drivers/usb/gadget/function/u_hid.h index 98d6af558c03..84bb70292855 100644 --- a/drivers/usb/gadget/function/u_hid.h +++ b/drivers/usb/gadget/function/u_hid.h @@ -20,6 +20,7 @@ struct f_hid_opts { int minor; unsigned char subclass; unsigned char protocol; + unsigned char no_out_endpoint; unsigned short report_length; unsigned short report_desc_length; unsigned char *report_desc; -- cgit v1.2.3 From cbf286e8ef8337308c259ff5b9ce2e74d403be5a Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 20 Aug 2021 15:34:58 +0300 Subject: xhci: fix unsafe memory usage in xhci tracing Removes static char buffer usage in the following decode functions: xhci_decode_trb() xhci_decode_ptortsc() Caller must provide a buffer to use. In tracing use __get_str() as recommended to pass buffer. Minor chanes are needed in xhci debugfs code as these functions are also used there. Changes include moving XHCI_MSG_MAX definititon from xhci-trace.h to xhci.h Cc: Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210820123503.2605901-2-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-debugfs.c | 6 +++-- drivers/usb/host/xhci-trace.h | 8 +++---- drivers/usb/host/xhci.h | 52 ++++++++++++++++++++++------------------- 3 files changed, 36 insertions(+), 30 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-debugfs.c b/drivers/usb/host/xhci-debugfs.c index 2c0fda57869e..85c12f56b17c 100644 --- a/drivers/usb/host/xhci-debugfs.c +++ b/drivers/usb/host/xhci-debugfs.c @@ -198,12 +198,13 @@ static void xhci_ring_dump_segment(struct seq_file *s, int i; dma_addr_t dma; union xhci_trb *trb; + char str[XHCI_MSG_MAX]; for (i = 0; i < TRBS_PER_SEGMENT; i++) { trb = &seg->trbs[i]; dma = seg->dma + i * sizeof(*trb); seq_printf(s, "%pad: %s\n", &dma, - xhci_decode_trb(le32_to_cpu(trb->generic.field[0]), + xhci_decode_trb(str, XHCI_MSG_MAX, le32_to_cpu(trb->generic.field[0]), le32_to_cpu(trb->generic.field[1]), le32_to_cpu(trb->generic.field[2]), le32_to_cpu(trb->generic.field[3]))); @@ -341,9 +342,10 @@ static int xhci_portsc_show(struct seq_file *s, void *unused) { struct xhci_port *port = s->private; u32 portsc; + char str[XHCI_MSG_MAX]; portsc = readl(port->addr); - seq_printf(s, "%s\n", xhci_decode_portsc(portsc)); + seq_printf(s, "%s\n", xhci_decode_portsc(str, portsc)); return 0; } diff --git a/drivers/usb/host/xhci-trace.h b/drivers/usb/host/xhci-trace.h index 627abd236dbe..5e1c50cb7016 100644 --- a/drivers/usb/host/xhci-trace.h +++ b/drivers/usb/host/xhci-trace.h @@ -25,8 +25,6 @@ #include "xhci.h" #include "xhci-dbgcap.h" -#define XHCI_MSG_MAX 500 - DECLARE_EVENT_CLASS(xhci_log_msg, TP_PROTO(struct va_format *vaf), TP_ARGS(vaf), @@ -122,6 +120,7 @@ DECLARE_EVENT_CLASS(xhci_log_trb, __field(u32, field1) __field(u32, field2) __field(u32, field3) + __dynamic_array(char, str, XHCI_MSG_MAX) ), TP_fast_assign( __entry->type = ring->type; @@ -131,7 +130,7 @@ DECLARE_EVENT_CLASS(xhci_log_trb, __entry->field3 = le32_to_cpu(trb->field[3]); ), TP_printk("%s: %s", xhci_ring_type_string(__entry->type), - xhci_decode_trb(__entry->field0, __entry->field1, + xhci_decode_trb(__get_str(str), XHCI_MSG_MAX, __entry->field0, __entry->field1, __entry->field2, __entry->field3) ) ); @@ -523,6 +522,7 @@ DECLARE_EVENT_CLASS(xhci_log_portsc, TP_STRUCT__entry( __field(u32, portnum) __field(u32, portsc) + __dynamic_array(char, str, XHCI_MSG_MAX) ), TP_fast_assign( __entry->portnum = portnum; @@ -530,7 +530,7 @@ DECLARE_EVENT_CLASS(xhci_log_portsc, ), TP_printk("port-%d: %s", __entry->portnum, - xhci_decode_portsc(__entry->portsc) + xhci_decode_portsc(__get_str(str), __entry->portsc) ) ); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 3c7d281672ae..99ae9994f5eb 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -22,6 +22,9 @@ #include "xhci-ext-caps.h" #include "pci-quirks.h" +/* max buffer size for trace and debug messages */ +#define XHCI_MSG_MAX 500 + /* xHCI PCI Configuration Registers */ #define XHCI_SBRN_OFFSET (0x60) @@ -2235,15 +2238,14 @@ static inline char *xhci_slot_state_string(u32 state) } } -static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, - u32 field3) +static inline const char *xhci_decode_trb(char *str, size_t size, + u32 field0, u32 field1, u32 field2, u32 field3) { - static char str[256]; int type = TRB_FIELD_TO_TYPE(field3); switch (type) { case TRB_LINK: - sprintf(str, + snprintf(str, size, "LINK %08x%08x intr %d type '%s' flags %c:%c:%c:%c", field1, field0, GET_INTR_TARGET(field2), xhci_trb_type_string(type), @@ -2260,7 +2262,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, case TRB_HC_EVENT: case TRB_DEV_NOTE: case TRB_MFINDEX_WRAP: - sprintf(str, + snprintf(str, size, "TRB %08x%08x status '%s' len %d slot %d ep %d type '%s' flags %c:%c", field1, field0, xhci_trb_comp_code_string(GET_COMP_CODE(field2)), @@ -2273,7 +2275,8 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, break; case TRB_SETUP: - sprintf(str, "bRequestType %02x bRequest %02x wValue %02x%02x wIndex %02x%02x wLength %d length %d TD size %d intr %d type '%s' flags %c:%c:%c", + snprintf(str, size, + "bRequestType %02x bRequest %02x wValue %02x%02x wIndex %02x%02x wLength %d length %d TD size %d intr %d type '%s' flags %c:%c:%c", field0 & 0xff, (field0 & 0xff00) >> 8, (field0 & 0xff000000) >> 24, @@ -2290,7 +2293,8 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_DATA: - sprintf(str, "Buffer %08x%08x length %d TD size %d intr %d type '%s' flags %c:%c:%c:%c:%c:%c:%c", + snprintf(str, size, + "Buffer %08x%08x length %d TD size %d intr %d type '%s' flags %c:%c:%c:%c:%c:%c:%c", field1, field0, TRB_LEN(field2), GET_TD_SIZE(field2), GET_INTR_TARGET(field2), xhci_trb_type_string(type), @@ -2303,7 +2307,8 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_STATUS: - sprintf(str, "Buffer %08x%08x length %d TD size %d intr %d type '%s' flags %c:%c:%c:%c", + snprintf(str, size, + "Buffer %08x%08x length %d TD size %d intr %d type '%s' flags %c:%c:%c:%c", field1, field0, TRB_LEN(field2), GET_TD_SIZE(field2), GET_INTR_TARGET(field2), xhci_trb_type_string(type), @@ -2316,7 +2321,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, case TRB_ISOC: case TRB_EVENT_DATA: case TRB_TR_NOOP: - sprintf(str, + snprintf(str, size, "Buffer %08x%08x length %d TD size %d intr %d type '%s' flags %c:%c:%c:%c:%c:%c:%c:%c", field1, field0, TRB_LEN(field2), GET_TD_SIZE(field2), GET_INTR_TARGET(field2), @@ -2333,21 +2338,21 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, case TRB_CMD_NOOP: case TRB_ENABLE_SLOT: - sprintf(str, + snprintf(str, size, "%s: flags %c", xhci_trb_type_string(type), field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_DISABLE_SLOT: case TRB_NEG_BANDWIDTH: - sprintf(str, + snprintf(str, size, "%s: slot %d flags %c", xhci_trb_type_string(type), TRB_TO_SLOT_ID(field3), field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_ADDR_DEV: - sprintf(str, + snprintf(str, size, "%s: ctx %08x%08x slot %d flags %c:%c", xhci_trb_type_string(type), field1, field0, @@ -2356,7 +2361,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_CONFIG_EP: - sprintf(str, + snprintf(str, size, "%s: ctx %08x%08x slot %d flags %c:%c", xhci_trb_type_string(type), field1, field0, @@ -2365,7 +2370,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_EVAL_CONTEXT: - sprintf(str, + snprintf(str, size, "%s: ctx %08x%08x slot %d flags %c", xhci_trb_type_string(type), field1, field0, @@ -2373,7 +2378,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_RESET_EP: - sprintf(str, + snprintf(str, size, "%s: ctx %08x%08x slot %d ep %d flags %c:%c", xhci_trb_type_string(type), field1, field0, @@ -2394,7 +2399,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_SET_DEQ: - sprintf(str, + snprintf(str, size, "%s: deq %08x%08x stream %d slot %d ep %d flags %c", xhci_trb_type_string(type), field1, field0, @@ -2405,14 +2410,14 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_RESET_DEV: - sprintf(str, + snprintf(str, size, "%s: slot %d flags %c", xhci_trb_type_string(type), TRB_TO_SLOT_ID(field3), field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_FORCE_EVENT: - sprintf(str, + snprintf(str, size, "%s: event %08x%08x vf intr %d vf id %d flags %c", xhci_trb_type_string(type), field1, field0, @@ -2421,14 +2426,14 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_SET_LT: - sprintf(str, + snprintf(str, size, "%s: belt %d flags %c", xhci_trb_type_string(type), TRB_TO_BELT(field3), field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_GET_BW: - sprintf(str, + snprintf(str, size, "%s: ctx %08x%08x slot %d speed %d flags %c", xhci_trb_type_string(type), field1, field0, @@ -2437,7 +2442,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_FORCE_HEADER: - sprintf(str, + snprintf(str, size, "%s: info %08x%08x%08x pkt type %d roothub port %d flags %c", xhci_trb_type_string(type), field2, field1, field0 & 0xffffffe0, @@ -2446,7 +2451,7 @@ static inline const char *xhci_decode_trb(u32 field0, u32 field1, u32 field2, field3 & TRB_CYCLE ? 'C' : 'c'); break; default: - sprintf(str, + snprintf(str, size, "type '%s' -> raw %08x %08x %08x %08x", xhci_trb_type_string(type), field0, field1, field2, field3); @@ -2571,9 +2576,8 @@ static inline const char *xhci_portsc_link_state_string(u32 portsc) return "Unknown"; } -static inline const char *xhci_decode_portsc(u32 portsc) +static inline const char *xhci_decode_portsc(char *str, u32 portsc) { - static char str[256]; int ret; ret = sprintf(str, "%s %s %s Link:%s PortSpeed:%d ", -- cgit v1.2.3 From 4843b4b5ec64b875a5e334f280508f1f75e7d3e4 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 20 Aug 2021 15:34:59 +0300 Subject: xhci: fix even more unsafe memory usage in xhci tracing Removes static char buffer usage in the following decode functions: xhci_decode_ctrl_ctx() xhci_decode_slot_context() xhci_decode_usbsts() xhci_decode_doorbell() xhci_decode_ep_context() Caller must provide a buffer to use. In tracing use __get_str() as recommended to pass buffer. Minor changes are needed in other xhci code as these functions are also used elsewhere Cc: Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210820123503.2605901-3-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-debugfs.c | 8 ++++++-- drivers/usb/host/xhci-ring.c | 3 ++- drivers/usb/host/xhci-trace.h | 18 +++++++++++------- drivers/usb/host/xhci.h | 21 ++++++++------------- 4 files changed, 27 insertions(+), 23 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-debugfs.c b/drivers/usb/host/xhci-debugfs.c index 85c12f56b17c..dc832ddf7033 100644 --- a/drivers/usb/host/xhci-debugfs.c +++ b/drivers/usb/host/xhci-debugfs.c @@ -261,11 +261,13 @@ static int xhci_slot_context_show(struct seq_file *s, void *unused) struct xhci_slot_ctx *slot_ctx; struct xhci_slot_priv *priv = s->private; struct xhci_virt_device *dev = priv->dev; + char str[XHCI_MSG_MAX]; xhci = hcd_to_xhci(bus_to_hcd(dev->udev->bus)); slot_ctx = xhci_get_slot_ctx(xhci, dev->out_ctx); seq_printf(s, "%pad: %s\n", &dev->out_ctx->dma, - xhci_decode_slot_context(le32_to_cpu(slot_ctx->dev_info), + xhci_decode_slot_context(str, + le32_to_cpu(slot_ctx->dev_info), le32_to_cpu(slot_ctx->dev_info2), le32_to_cpu(slot_ctx->tt_info), le32_to_cpu(slot_ctx->dev_state))); @@ -281,6 +283,7 @@ static int xhci_endpoint_context_show(struct seq_file *s, void *unused) struct xhci_ep_ctx *ep_ctx; struct xhci_slot_priv *priv = s->private; struct xhci_virt_device *dev = priv->dev; + char str[XHCI_MSG_MAX]; xhci = hcd_to_xhci(bus_to_hcd(dev->udev->bus)); @@ -288,7 +291,8 @@ static int xhci_endpoint_context_show(struct seq_file *s, void *unused) ep_ctx = xhci_get_ep_ctx(xhci, dev->out_ctx, ep_index); dma = dev->out_ctx->dma + (ep_index + 1) * CTX_SIZE(xhci->hcc_params); seq_printf(s, "%pad: %s\n", &dma, - xhci_decode_ep_context(le32_to_cpu(ep_ctx->ep_info), + xhci_decode_ep_context(str, + le32_to_cpu(ep_ctx->ep_info), le32_to_cpu(ep_ctx->ep_info2), le64_to_cpu(ep_ctx->deq), le32_to_cpu(ep_ctx->tx_info))); diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 8fea44bbc266..d0faa67a689d 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1212,6 +1212,7 @@ void xhci_stop_endpoint_command_watchdog(struct timer_list *t) struct xhci_hcd *xhci = ep->xhci; unsigned long flags; u32 usbsts; + char str[XHCI_MSG_MAX]; spin_lock_irqsave(&xhci->lock, flags); @@ -1225,7 +1226,7 @@ void xhci_stop_endpoint_command_watchdog(struct timer_list *t) usbsts = readl(&xhci->op_regs->status); xhci_warn(xhci, "xHCI host not responding to stop endpoint command.\n"); - xhci_warn(xhci, "USBSTS:%s\n", xhci_decode_usbsts(usbsts)); + xhci_warn(xhci, "USBSTS:%s\n", xhci_decode_usbsts(str, usbsts)); ep->ep_state &= ~EP_STOP_CMD_PENDING; diff --git a/drivers/usb/host/xhci-trace.h b/drivers/usb/host/xhci-trace.h index 5e1c50cb7016..a5da02077297 100644 --- a/drivers/usb/host/xhci-trace.h +++ b/drivers/usb/host/xhci-trace.h @@ -322,6 +322,7 @@ DECLARE_EVENT_CLASS(xhci_log_ep_ctx, __field(u32, info2) __field(u64, deq) __field(u32, tx_info) + __dynamic_array(char, str, XHCI_MSG_MAX) ), TP_fast_assign( __entry->info = le32_to_cpu(ctx->ep_info); @@ -329,8 +330,8 @@ DECLARE_EVENT_CLASS(xhci_log_ep_ctx, __entry->deq = le64_to_cpu(ctx->deq); __entry->tx_info = le32_to_cpu(ctx->tx_info); ), - TP_printk("%s", xhci_decode_ep_context(__entry->info, - __entry->info2, __entry->deq, __entry->tx_info) + TP_printk("%s", xhci_decode_ep_context(__get_str(str), + __entry->info, __entry->info2, __entry->deq, __entry->tx_info) ) ); @@ -367,6 +368,7 @@ DECLARE_EVENT_CLASS(xhci_log_slot_ctx, __field(u32, info2) __field(u32, tt_info) __field(u32, state) + __dynamic_array(char, str, XHCI_MSG_MAX) ), TP_fast_assign( __entry->info = le32_to_cpu(ctx->dev_info); @@ -374,9 +376,9 @@ DECLARE_EVENT_CLASS(xhci_log_slot_ctx, __entry->tt_info = le64_to_cpu(ctx->tt_info); __entry->state = le32_to_cpu(ctx->dev_state); ), - TP_printk("%s", xhci_decode_slot_context(__entry->info, - __entry->info2, __entry->tt_info, - __entry->state) + TP_printk("%s", xhci_decode_slot_context(__get_str(str), + __entry->info, __entry->info2, + __entry->tt_info, __entry->state) ) ); @@ -431,12 +433,13 @@ DECLARE_EVENT_CLASS(xhci_log_ctrl_ctx, TP_STRUCT__entry( __field(u32, drop) __field(u32, add) + __dynamic_array(char, str, XHCI_MSG_MAX) ), TP_fast_assign( __entry->drop = le32_to_cpu(ctrl_ctx->drop_flags); __entry->add = le32_to_cpu(ctrl_ctx->add_flags); ), - TP_printk("%s", xhci_decode_ctrl_ctx(__entry->drop, __entry->add) + TP_printk("%s", xhci_decode_ctrl_ctx(__get_str(str), __entry->drop, __entry->add) ) ); @@ -555,13 +558,14 @@ DECLARE_EVENT_CLASS(xhci_log_doorbell, TP_STRUCT__entry( __field(u32, slot) __field(u32, doorbell) + __dynamic_array(char, str, XHCI_MSG_MAX) ), TP_fast_assign( __entry->slot = slot; __entry->doorbell = doorbell; ), TP_printk("Ring doorbell for %s", - xhci_decode_doorbell(__entry->slot, __entry->doorbell) + xhci_decode_doorbell(__get_str(str), __entry->slot, __entry->doorbell) ) ); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 99ae9994f5eb..dca6181c33fd 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -2460,10 +2460,9 @@ static inline const char *xhci_decode_trb(char *str, size_t size, return str; } -static inline const char *xhci_decode_ctrl_ctx(unsigned long drop, - unsigned long add) +static inline const char *xhci_decode_ctrl_ctx(char *str, + unsigned long drop, unsigned long add) { - static char str[1024]; unsigned int bit; int ret = 0; @@ -2489,10 +2488,9 @@ static inline const char *xhci_decode_ctrl_ctx(unsigned long drop, return str; } -static inline const char *xhci_decode_slot_context(u32 info, u32 info2, - u32 tt_info, u32 state) +static inline const char *xhci_decode_slot_context(char *str, + u32 info, u32 info2, u32 tt_info, u32 state) { - static char str[1024]; u32 speed; u32 hub; u32 mtt; @@ -2621,9 +2619,8 @@ static inline const char *xhci_decode_portsc(char *str, u32 portsc) return str; } -static inline const char *xhci_decode_usbsts(u32 usbsts) +static inline const char *xhci_decode_usbsts(char *str, u32 usbsts) { - static char str[256]; int ret = 0; if (usbsts == ~(u32)0) @@ -2650,9 +2647,8 @@ static inline const char *xhci_decode_usbsts(u32 usbsts) return str; } -static inline const char *xhci_decode_doorbell(u32 slot, u32 doorbell) +static inline const char *xhci_decode_doorbell(char *str, u32 slot, u32 doorbell) { - static char str[256]; u8 ep; u16 stream; int ret; @@ -2719,10 +2715,9 @@ static inline const char *xhci_ep_type_string(u8 type) } } -static inline const char *xhci_decode_ep_context(u32 info, u32 info2, u64 deq, - u32 tx_info) +static inline const char *xhci_decode_ep_context(char *str, u32 info, + u32 info2, u64 deq, u32 tx_info) { - static char str[1024]; int ret; u32 esit; -- cgit v1.2.3 From 94f339147fc3eb9edef7ee4ef6e39c569c073753 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 20 Aug 2021 15:35:00 +0300 Subject: xhci: Fix failure to give back some cached cancelled URBs. Only TDs with status TD_CLEARING_CACHE will be given back after cache is cleared with a set TR deq command. xhci_invalidate_cached_td() failed to set the TD_CLEARING_CACHE status for some cancelled TDs as it assumed an endpoint only needs to clear the TD it stopped on. This isn't always true. For example with streams enabled an endpoint may have several stream rings, each stopping on a different TDs. Note that if an endpoint has several stream rings, the current code will still only clear the cache of the stream pointed to by the last cancelled TD in the cancel list. This patch only focus on making sure all canceled TDs are given back, avoiding hung task after device removal. Another fix to solve clearing the caches of all stream rings with cancelled TDs is needed, but not as urgent. This issue was simultanously discovered and debugged by by Tao Wang, with a slightly different fix proposal. Fixes: 674f8438c121 ("xhci: split handling halted endpoints into two steps") Cc: #5.12 Reported-by: Tao Wang Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210820123503.2605901-4-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index d0faa67a689d..9017986241f5 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -942,17 +942,21 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) td->urb->stream_id); hw_deq &= ~0xf; - if (td->cancel_status == TD_HALTED) { - cached_td = td; - } else if (trb_in_td(xhci, td->start_seg, td->first_trb, - td->last_trb, hw_deq, false)) { + if (td->cancel_status == TD_HALTED || + trb_in_td(xhci, td->start_seg, td->first_trb, td->last_trb, hw_deq, false)) { switch (td->cancel_status) { case TD_CLEARED: /* TD is already no-op */ case TD_CLEARING_CACHE: /* set TR deq command already queued */ break; case TD_DIRTY: /* TD is cached, clear it */ case TD_HALTED: - /* FIXME stream case, several stopped rings */ + td->cancel_status = TD_CLEARING_CACHE; + if (cached_td) + /* FIXME stream case, several stopped rings */ + xhci_dbg(xhci, + "Move dq past stream %u URB %p instead of stream %u URB %p\n", + td->urb->stream_id, td->urb, + cached_td->urb->stream_id, cached_td->urb); cached_td = td; break; } @@ -961,18 +965,24 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) td->cancel_status = TD_CLEARED; } } - if (cached_td) { - cached_td->cancel_status = TD_CLEARING_CACHE; - err = xhci_move_dequeue_past_td(xhci, slot_id, ep->ep_index, - cached_td->urb->stream_id, - cached_td); - /* Failed to move past cached td, try just setting it noop */ - if (err) { - td_to_noop(xhci, ring, cached_td, false); - cached_td->cancel_status = TD_CLEARED; + /* If there's no need to move the dequeue pointer then we're done */ + if (!cached_td) + return 0; + + err = xhci_move_dequeue_past_td(xhci, slot_id, ep->ep_index, + cached_td->urb->stream_id, + cached_td); + if (err) { + /* Failed to move past cached td, just set cached TDs to no-op */ + list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { + if (td->cancel_status != TD_CLEARING_CACHE) + continue; + xhci_dbg(xhci, "Failed to clear cancelled cached URB %p, mark clear anyway\n", + td->urb); + td_to_noop(xhci, ring, td, false); + td->cancel_status = TD_CLEARED; } - cached_td = NULL; } return 0; } -- cgit v1.2.3 From 2847c46c61486fd8bca9136a6e27177212e78c69 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 20 Aug 2021 15:35:01 +0300 Subject: Revert "USB: xhci: fix U1/U2 handling for hardware with XHCI_INTEL_HOST quirk set" This reverts commit 5d5323a6f3625f101dbfa94ba3ef7706cce38760. That commit effectively disabled Intel host initiated U1/U2 lpm for devices with periodic endpoints. Before that commit we disabled host initiated U1/U2 lpm if the exit latency was larger than any periodic endpoint service interval, this is according to xhci spec xhci 1.1 specification section 4.23.5.2 After that commit we incorrectly checked that service interval was smaller than U1/U2 inactivity timeout. This is not relevant, and can't happen for Intel hosts as previously set U1/U2 timeout = 105% * service interval. Patch claimed it solved cases where devices can't be enumerated because of bandwidth issues. This might be true but it's a side effect of accidentally turning off lpm. exit latency calculations have been revised since then Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210820123503.2605901-5-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 3618070eba78..18a203c9011e 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -4705,19 +4705,19 @@ static u16 xhci_calculate_u1_timeout(struct xhci_hcd *xhci, { unsigned long long timeout_ns; - if (xhci->quirks & XHCI_INTEL_HOST) - timeout_ns = xhci_calculate_intel_u1_timeout(udev, desc); - else - timeout_ns = udev->u1_params.sel; - /* Prevent U1 if service interval is shorter than U1 exit latency */ if (usb_endpoint_xfer_int(desc) || usb_endpoint_xfer_isoc(desc)) { - if (xhci_service_interval_to_ns(desc) <= timeout_ns) { + if (xhci_service_interval_to_ns(desc) <= udev->u1_params.mel) { dev_dbg(&udev->dev, "Disable U1, ESIT shorter than exit latency\n"); return USB3_LPM_DISABLED; } } + if (xhci->quirks & XHCI_INTEL_HOST) + timeout_ns = xhci_calculate_intel_u1_timeout(udev, desc); + else + timeout_ns = udev->u1_params.sel; + /* The U1 timeout is encoded in 1us intervals. * Don't return a timeout of zero, because that's USB3_LPM_DISABLED. */ @@ -4769,19 +4769,19 @@ static u16 xhci_calculate_u2_timeout(struct xhci_hcd *xhci, { unsigned long long timeout_ns; - if (xhci->quirks & XHCI_INTEL_HOST) - timeout_ns = xhci_calculate_intel_u2_timeout(udev, desc); - else - timeout_ns = udev->u2_params.sel; - /* Prevent U2 if service interval is shorter than U2 exit latency */ if (usb_endpoint_xfer_int(desc) || usb_endpoint_xfer_isoc(desc)) { - if (xhci_service_interval_to_ns(desc) <= timeout_ns) { + if (xhci_service_interval_to_ns(desc) <= udev->u2_params.mel) { dev_dbg(&udev->dev, "Disable U2, ESIT shorter than exit latency\n"); return USB3_LPM_DISABLED; } } + if (xhci->quirks & XHCI_INTEL_HOST) + timeout_ns = xhci_calculate_intel_u2_timeout(udev, desc); + else + timeout_ns = udev->u2_params.sel; + /* The U2 timeout is encoded in 256us intervals */ timeout_ns = DIV_ROUND_UP_ULL(timeout_ns, 256 * 1000); /* If the necessary timeout value is bigger than what we can set in the -- cgit v1.2.3 From 0d9b9f533bf1aa555fcd28fa459332b7731316b3 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 20 Aug 2021 15:35:02 +0300 Subject: xhci: Add additional dynamic debug to follow URBs in cancel and error cases. Add more debugging messages to follow what happends to a URB internally in special cases like URB cancel, halted endpoints and endpoint reset. Helps tracking issues like URB never given back by host. Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210820123503.2605901-6-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 9017986241f5..8be4ba3758b1 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -830,9 +830,14 @@ static void xhci_giveback_invalidated_tds(struct xhci_virt_ep *ep) ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb); - if (td->cancel_status == TD_CLEARED) + if (td->cancel_status == TD_CLEARED) { + xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n", + __func__, td->urb); xhci_td_cleanup(ep->xhci, td, ring, td->status); - + } else { + xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", + __func__, td->urb, td->cancel_status); + } if (ep->xhci->xhc_state & XHCI_STATE_DYING) return; } @@ -850,6 +855,10 @@ static int xhci_reset_halted_ep(struct xhci_hcd *xhci, unsigned int slot_id, goto done; } + xhci_dbg(xhci, "%s-reset ep %u, slot %u\n", + (reset_type == EP_HARD_RESET) ? "Hard" : "Soft", + ep_index, slot_id); + ret = xhci_queue_reset_ep(xhci, command, slot_id, ep_index, reset_type); done: if (ret) @@ -883,7 +892,8 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci, } if (ep->ep_state & EP_HALTED) { - xhci_dbg(xhci, "Reset ep command already pending\n"); + xhci_dbg(xhci, "Reset ep command for ep_index %d already pending\n", + ep->ep_index); return 0; } @@ -922,9 +932,10 @@ static int xhci_invalidate_cancelled_tds(struct xhci_virt_ep *ep) list_for_each_entry_safe(td, tmp_td, &ep->cancelled_td_list, cancelled_td_list) { xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, - "Removing canceled TD starting at 0x%llx (dma).", - (unsigned long long)xhci_trb_virt_to_dma( - td->start_seg, td->first_trb)); + "Removing canceled TD starting at 0x%llx (dma) in stream %u URB %p", + (unsigned long long)xhci_trb_virt_to_dma( + td->start_seg, td->first_trb), + td->urb->stream_id, td->urb); list_del_init(&td->td_list); ring = xhci_urb_to_transfer_ring(xhci, td->urb); if (!ring) { @@ -1079,6 +1090,8 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, return; case EP_STATE_RUNNING: /* Race, HW handled stop ep cmd before ep was running */ + xhci_dbg(xhci, "Stop ep completion ctx error, ep is running\n"); + command = xhci_alloc_command(xhci, false, GFP_ATOMIC); if (!command) xhci_stop_watchdog_timer_in_irq(xhci, ep); @@ -1400,7 +1413,12 @@ static void xhci_handle_cmd_set_deq(struct xhci_hcd *xhci, int slot_id, ep_ring = xhci_urb_to_transfer_ring(ep->xhci, td->urb); if (td->cancel_status == TD_CLEARING_CACHE) { td->cancel_status = TD_CLEARED; + xhci_dbg(ep->xhci, "%s: Giveback cancelled URB %p TD\n", + __func__, td->urb); xhci_td_cleanup(ep->xhci, td, ep_ring, td->status); + } else { + xhci_dbg(ep->xhci, "%s: Keep cancelled URB %p TD as cancel_status is %d\n", + __func__, td->urb, td->cancel_status); } } cleanup: -- cgit v1.2.3 From 669bc5a188b40a4edc9c2a42e5b32f19182767d9 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 20 Aug 2021 15:35:03 +0300 Subject: xhci: Add bus number to some debug messages As we register two usb buses for each xHC, and systems with several hosts are more and more common it is getting hard to follow the flow of debug messages without knowing which bus they belong to Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20210820123503.2605901-7-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 6 ++++-- drivers/usb/host/xhci-ring.c | 3 ++- drivers/usb/host/xhci.c | 6 ++++-- 3 files changed, 10 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 151e93c4bd57..a3f875eea751 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -1667,7 +1667,8 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf) status = 1; } if (!status && !reset_change) { - xhci_dbg(xhci, "%s: stopping port polling.\n", __func__); + xhci_dbg(xhci, "%s: stopping usb%d port polling\n", + __func__, hcd->self.busnum); clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); } spin_unlock_irqrestore(&xhci->lock, flags); @@ -1699,7 +1700,8 @@ int xhci_bus_suspend(struct usb_hcd *hcd) if (bus_state->resuming_ports || /* USB2 */ bus_state->port_remote_wakeup) { /* USB3 */ spin_unlock_irqrestore(&xhci->lock, flags); - xhci_dbg(xhci, "suspend failed because a port is resuming\n"); + xhci_dbg(xhci, "usb%d bus suspend to fail because a port is resuming\n", + hcd->self.busnum); return -EBUSY; } } diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 8be4ba3758b1..e676749f543b 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2031,7 +2031,8 @@ cleanup: * bits are still set. When an event occurs, switch over to * polling to avoid losing status changes. */ - xhci_dbg(xhci, "%s: starting port polling.\n", __func__); + xhci_dbg(xhci, "%s: starting usb%d port polling.\n", + __func__, hcd->self.busnum); set_bit(HCD_FLAG_POLL_RH, &hcd->flags); spin_unlock(&xhci->lock); /* Pass this up to the core */ diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 18a203c9011e..f3dabd02382c 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -993,7 +993,8 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup) xhci_dbc_suspend(xhci); /* Don't poll the roothubs on bus suspend. */ - xhci_dbg(xhci, "%s: stopping port polling.\n", __func__); + xhci_dbg(xhci, "%s: stopping usb%d port polling.\n", + __func__, hcd->self.busnum); clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); del_timer_sync(&hcd->rh_timer); clear_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags); @@ -1257,7 +1258,8 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) usb_asmedia_modifyflowcontrol(to_pci_dev(hcd->self.controller)); /* Re-enable port polling. */ - xhci_dbg(xhci, "%s: starting port polling.\n", __func__); + xhci_dbg(xhci, "%s: starting usb%d port polling.\n", + __func__, hcd->self.busnum); set_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags); usb_hcd_poll_rh_status(xhci->shared_hcd); set_bit(HCD_FLAG_POLL_RH, &hcd->flags); -- cgit v1.2.3 From 4720f1bf4ee4a784d9ece05420ba33c9222a3004 Mon Sep 17 00:00:00 2001 From: Evgeny Novikov Date: Wed, 25 Aug 2021 20:09:02 +0300 Subject: usb: ehci-orion: Handle errors of clk_prepare_enable() in probe ehci_orion_drv_probe() did not account for possible errors of clk_prepare_enable() that in particular could cause invocation of clk_disable_unprepare() on clocks that were not prepared/enabled yet, e.g. in remove or on handling errors of usb_add_hcd() in probe. Though, there were several patches fixing different issues with clocks in this driver, they did not solve this problem. Add handling of errors of clk_prepare_enable() in ehci_orion_drv_probe() to avoid calls of clk_disable_unprepare() without previous successful invocation of clk_prepare_enable(). Found by Linux Driver Verification project (linuxtesting.org). Fixes: 8c869edaee07 ("ARM: Orion: EHCI: Add support for enabling clocks") Co-developed-by: Kirill Shilimanov Reviewed-by: Andrew Lunn Acked-by: Alan Stern Signed-off-by: Evgeny Novikov Signed-off-by: Kirill Shilimanov Link: https://lore.kernel.org/r/20210825170902.11234-1-novikov@ispras.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-orion.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/ehci-orion.c b/drivers/usb/host/ehci-orion.c index a319b1df3011..3626758b3e2a 100644 --- a/drivers/usb/host/ehci-orion.c +++ b/drivers/usb/host/ehci-orion.c @@ -264,8 +264,11 @@ static int ehci_orion_drv_probe(struct platform_device *pdev) * the clock does not exists. */ priv->clk = devm_clk_get(&pdev->dev, NULL); - if (!IS_ERR(priv->clk)) - clk_prepare_enable(priv->clk); + if (!IS_ERR(priv->clk)) { + err = clk_prepare_enable(priv->clk); + if (err) + goto err_put_hcd; + } priv->phy = devm_phy_optional_get(&pdev->dev, "usb"); if (IS_ERR(priv->phy)) { @@ -311,6 +314,7 @@ static int ehci_orion_drv_probe(struct platform_device *pdev) err_dis_clk: if (!IS_ERR(priv->clk)) clk_disable_unprepare(priv->clk); +err_put_hcd: usb_put_hcd(hcd); err: dev_err(&pdev->dev, "init %s fail, %d\n", -- cgit v1.2.3 From 6a48d0ae01a6ab05ae5e78328546a2f5f6d3054a Mon Sep 17 00:00:00 2001 From: Nadezda Lutovinova Date: Thu, 19 Aug 2021 18:48:18 +0300 Subject: usb: dwc3: imx8mp: request irq after initializing dwc3 If IRQ occurs between calling devm_request_threaded_irq() and initializing dwc3_imx->dwc3, then null pointer dereference occurs since dwc3_imx->dwc3 is used in dwc3_imx8mp_interrupt(). The patch puts registration of the interrupt handler after initializing of neccesery data. Found by Linux Driver Verification project (linuxtesting.org). Reviewed-by: Fabio Estevam Acked-by: Felipe Balbi Signed-off-by: Nadezda Lutovinova Link: https://lore.kernel.org/r/20210819154818.18334-1-lutovinova@ispras.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-imx8mp.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/dwc3/dwc3-imx8mp.c b/drivers/usb/dwc3/dwc3-imx8mp.c index 756faa46d33a..d328d20abfbc 100644 --- a/drivers/usb/dwc3/dwc3-imx8mp.c +++ b/drivers/usb/dwc3/dwc3-imx8mp.c @@ -152,13 +152,6 @@ static int dwc3_imx8mp_probe(struct platform_device *pdev) } dwc3_imx->irq = irq; - err = devm_request_threaded_irq(dev, irq, NULL, dwc3_imx8mp_interrupt, - IRQF_ONESHOT, dev_name(dev), dwc3_imx); - if (err) { - dev_err(dev, "failed to request IRQ #%d --> %d\n", irq, err); - goto disable_clks; - } - pm_runtime_set_active(dev); pm_runtime_enable(dev); err = pm_runtime_get_sync(dev); @@ -186,6 +179,13 @@ static int dwc3_imx8mp_probe(struct platform_device *pdev) } of_node_put(dwc3_np); + err = devm_request_threaded_irq(dev, irq, NULL, dwc3_imx8mp_interrupt, + IRQF_ONESHOT, dev_name(dev), dwc3_imx); + if (err) { + dev_err(dev, "failed to request IRQ #%d --> %d\n", irq, err); + goto depopulate; + } + device_set_wakeup_capable(dev, true); pm_runtime_put(dev); -- cgit v1.2.3 From 1abade64563ef5388db545b55cf158e849f6e717 Mon Sep 17 00:00:00 2001 From: Nehal Bakulchandra Shah Date: Tue, 24 Aug 2021 00:14:48 +0530 Subject: usb: dwc3: pci: add support for AMD's newer generation platform. AMD's latest platforms has DWC3 controller. Add the PCI ID and properties for the same. Signed-off-by: Nehal Bakulchandra Shah Acked-by: Felipe Balbi Link: https://lore.kernel.org/r/20210823184449.2796184-2-Nehal-Bakulchandra.shah@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-pci.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c index 2b37bdd39a74..7ff8fc8f79a9 100644 --- a/drivers/usb/dwc3/dwc3-pci.c +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -44,6 +44,7 @@ #define PCI_DEVICE_ID_INTEL_ADLM 0x54ee #define PCI_DEVICE_ID_INTEL_ADLS 0x7ae1 #define PCI_DEVICE_ID_INTEL_TGL 0x9a15 +#define PCI_DEVICE_ID_AMD_MR 0x163a #define PCI_INTEL_BXT_DSM_GUID "732b85d5-b7a7-4a1b-9ba0-4bbd00ffd511" #define PCI_INTEL_BXT_FUNC_PMU_PWR 4 @@ -148,6 +149,14 @@ static const struct property_entry dwc3_pci_amd_properties[] = { {} }; +static const struct property_entry dwc3_pci_mr_properties[] = { + PROPERTY_ENTRY_STRING("dr_mode", "otg"), + PROPERTY_ENTRY_BOOL("usb-role-switch"), + PROPERTY_ENTRY_STRING("role-switch-default-mode", "host"), + PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent"), + {} +}; + static const struct software_node dwc3_pci_intel_swnode = { .properties = dwc3_pci_intel_properties, }; @@ -160,6 +169,10 @@ static const struct software_node dwc3_pci_amd_swnode = { .properties = dwc3_pci_amd_properties, }; +static const struct software_node dwc3_pci_amd_mr_swnode = { + .properties = dwc3_pci_mr_properties, +}; + static int dwc3_pci_quirks(struct dwc3_pci *dwc) { struct pci_dev *pdev = dwc->pci; @@ -401,6 +414,10 @@ static const struct pci_device_id dwc3_pci_id_table[] = { { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_NL_USB), (kernel_ulong_t) &dwc3_pci_amd_swnode, }, + + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_MR), + (kernel_ulong_t)&dwc3_pci_amd_mr_swnode, }, + { } /* Terminating Entry */ }; MODULE_DEVICE_TABLE(pci, dwc3_pci_id_table); -- cgit v1.2.3 From 7c75bde329d7e2a93cf86a5c15c61f96f1446cdc Mon Sep 17 00:00:00 2001 From: Nadezda Lutovinova Date: Thu, 19 Aug 2021 19:33:23 +0300 Subject: usb: musb: musb_dsps: request_irq() after initializing musb If IRQ occurs between calling dsps_setup_optional_vbus_irq() and dsps_create_musb_pdev(), then null pointer dereference occurs since glue->musb wasn't initialized yet. The patch puts initializing of neccesery data before registration of the interrupt handler. Found by Linux Driver Verification project (linuxtesting.org). Signed-off-by: Nadezda Lutovinova Link: https://lore.kernel.org/r/20210819163323.17714-1-lutovinova@ispras.ru Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/musb_dsps.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/musb/musb_dsps.c b/drivers/usb/musb/musb_dsps.c index 5892f3ce0cdc..ce9fc46c9266 100644 --- a/drivers/usb/musb/musb_dsps.c +++ b/drivers/usb/musb/musb_dsps.c @@ -890,23 +890,22 @@ static int dsps_probe(struct platform_device *pdev) if (!glue->usbss_base) return -ENXIO; - if (usb_get_dr_mode(&pdev->dev) == USB_DR_MODE_PERIPHERAL) { - ret = dsps_setup_optional_vbus_irq(pdev, glue); - if (ret) - goto err_iounmap; - } - platform_set_drvdata(pdev, glue); pm_runtime_enable(&pdev->dev); ret = dsps_create_musb_pdev(glue, pdev); if (ret) goto err; + if (usb_get_dr_mode(&pdev->dev) == USB_DR_MODE_PERIPHERAL) { + ret = dsps_setup_optional_vbus_irq(pdev, glue); + if (ret) + goto err; + } + return 0; err: pm_runtime_disable(&pdev->dev); -err_iounmap: iounmap(glue->usbss_base); return ret; } -- cgit v1.2.3 From 0b9f6cc845ce8ea4ee72bfabbaf9bfbf68f5fbde Mon Sep 17 00:00:00 2001 From: Cai Huoqing Date: Sun, 22 Aug 2021 12:30:05 +0800 Subject: usb: gadget: mass_storage: Remove repeated verbose license text remove it because SPDX-License-Identifier is already used Signed-off-by: Cai Huoqing Link: https://lore.kernel.org/r/20210822043005.192-1-caihuoqing@baidu.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_mass_storage.c | 30 ---------------------------- 1 file changed, 30 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index 4a4703634a2a..6ad669dde41c 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -6,36 +6,6 @@ * Copyright (C) 2009 Samsung Electronics * Author: Michal Nazarewicz * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions, and the following disclaimer, - * without modification. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. The names of the above-listed copyright holders may not be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * ALTERNATIVELY, this software may be distributed under the terms of the - * GNU General Public License ("GPL") as published by the Free Software - * Foundation, either version 2 of that License or (at your option) any - * later version. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* -- cgit v1.2.3 From 5786b433f721059f65b55a5ed0520ff5c375697d Mon Sep 17 00:00:00 2001 From: Cai Huoqing Date: Mon, 23 Aug 2021 12:58:07 +0800 Subject: usb: gadget: aspeed: Remove repeated verbose license text remove it because SPDX-License-Identifier is already used Signed-off-by: Cai Huoqing Link: https://lore.kernel.org/r/20210823045807.49-1-caihuoqing@baidu.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/aspeed-vhub/core.c | 5 ----- drivers/usb/gadget/udc/aspeed-vhub/dev.c | 5 ----- drivers/usb/gadget/udc/aspeed-vhub/ep0.c | 5 ----- drivers/usb/gadget/udc/aspeed-vhub/epn.c | 5 ----- drivers/usb/gadget/udc/aspeed-vhub/hub.c | 5 ----- 5 files changed, 25 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/aspeed-vhub/core.c b/drivers/usb/gadget/udc/aspeed-vhub/core.c index d11d3d14313f..7a635c499777 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/core.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/core.c @@ -5,11 +5,6 @@ * core.c - Top level support * * Copyright 2017 IBM Corporation - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #include diff --git a/drivers/usb/gadget/udc/aspeed-vhub/dev.c b/drivers/usb/gadget/udc/aspeed-vhub/dev.c index d268306a7bfe..d918e8b2af3c 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/dev.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/dev.c @@ -5,11 +5,6 @@ * dev.c - Individual device/gadget management (ie, a port = a gadget) * * Copyright 2017 IBM Corporation - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #include diff --git a/drivers/usb/gadget/udc/aspeed-vhub/ep0.c b/drivers/usb/gadget/udc/aspeed-vhub/ep0.c index 022b777b85f8..74ea36c19b1e 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/ep0.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/ep0.c @@ -5,11 +5,6 @@ * ep0.c - Endpoint 0 handling * * Copyright 2017 IBM Corporation - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #include diff --git a/drivers/usb/gadget/udc/aspeed-vhub/epn.c b/drivers/usb/gadget/udc/aspeed-vhub/epn.c index cb164c615e6f..917892ca8753 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/epn.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/epn.c @@ -5,11 +5,6 @@ * epn.c - Generic endpoints management * * Copyright 2017 IBM Corporation - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #include diff --git a/drivers/usb/gadget/udc/aspeed-vhub/hub.c b/drivers/usb/gadget/udc/aspeed-vhub/hub.c index 5c7dea5e0ff1..b9960fdd8a51 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/hub.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c @@ -5,11 +5,6 @@ * hub.c - virtual hub handling * * Copyright 2017 IBM Corporation - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #include -- cgit v1.2.3 From 258c81b341c8025d79073ce2d6ce19dcdc7d10d2 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Sat, 21 Aug 2021 00:31:21 +0530 Subject: usbip: give back URBs for unsent unlink requests during cleanup In vhci_device_unlink_cleanup(), the URBs for unsent unlink requests are not given back. This sometimes causes usb_kill_urb to wait indefinitely for that urb to be given back. syzbot has reported a hung task issue [1] for this. To fix this, give back the urbs corresponding to unsent unlink requests (unlink_tx list) similar to how urbs corresponding to unanswered unlink requests (unlink_rx list) are given back. [1]: https://syzkaller.appspot.com/bug?id=08f12df95ae7da69814e64eb5515d5a85ed06b76 Reported-by: syzbot+74d6ef051d3d2eacf428@syzkaller.appspotmail.com Tested-by: syzbot+74d6ef051d3d2eacf428@syzkaller.appspotmail.com Reviewed-by: Shuah Khan Signed-off-by: Anirudh Rayabharam Link: https://lore.kernel.org/r/20210820190122.16379-2-mail@anirudhrb.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/vhci_hcd.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index 4ba6bcdaa8e9..190bd3d1c1f0 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -957,8 +957,32 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev) spin_lock(&vdev->priv_lock); list_for_each_entry_safe(unlink, tmp, &vdev->unlink_tx, list) { + struct urb *urb; + + /* give back urb of unsent unlink request */ pr_info("unlink cleanup tx %lu\n", unlink->unlink_seqnum); + + urb = pickup_urb_and_free_priv(vdev, unlink->unlink_seqnum); + if (!urb) { + list_del(&unlink->list); + kfree(unlink); + continue; + } + + urb->status = -ENODEV; + + usb_hcd_unlink_urb_from_ep(hcd, urb); + list_del(&unlink->list); + + spin_unlock(&vdev->priv_lock); + spin_unlock_irqrestore(&vhci->lock, flags); + + usb_hcd_giveback_urb(hcd, urb, urb->status); + + spin_lock_irqsave(&vhci->lock, flags); + spin_lock(&vdev->priv_lock); + kfree(unlink); } -- cgit v1.2.3 From 5289253b01d7bfa5e8ae23fdc2482acacd301e7d Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Sat, 21 Aug 2021 00:31:22 +0530 Subject: usbip: clean up code in vhci_device_unlink_cleanup The cleanup code for unlink_tx and unlink_rx lists is almost the same. So, extract it into a new function and call it for both unlink_rx and unlink_tx. Also, remove unnecessary log messages. Signed-off-by: Anirudh Rayabharam Link: https://lore.kernel.org/r/20210820190122.16379-3-mail@anirudhrb.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/vhci_hcd.c | 52 ++++++++++---------------------------------- 1 file changed, 12 insertions(+), 40 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index 190bd3d1c1f0..b5b31a1d40b6 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -945,7 +945,8 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) return 0; } -static void vhci_device_unlink_cleanup(struct vhci_device *vdev) +static void vhci_cleanup_unlink_list(struct vhci_device *vdev, + struct list_head *unlink_list) { struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); struct usb_hcd *hcd = vhci_hcd_to_hcd(vhci_hcd); @@ -956,12 +957,9 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev) spin_lock_irqsave(&vhci->lock, flags); spin_lock(&vdev->priv_lock); - list_for_each_entry_safe(unlink, tmp, &vdev->unlink_tx, list) { + list_for_each_entry_safe(unlink, tmp, unlink_list, list) { struct urb *urb; - /* give back urb of unsent unlink request */ - pr_info("unlink cleanup tx %lu\n", unlink->unlink_seqnum); - urb = pickup_urb_and_free_priv(vdev, unlink->unlink_seqnum); if (!urb) { list_del(&unlink->list); @@ -986,45 +984,19 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev) kfree(unlink); } - while (!list_empty(&vdev->unlink_rx)) { - struct urb *urb; - - unlink = list_first_entry(&vdev->unlink_rx, struct vhci_unlink, - list); - - /* give back URB of unanswered unlink request */ - pr_info("unlink cleanup rx %lu\n", unlink->unlink_seqnum); - - urb = pickup_urb_and_free_priv(vdev, unlink->unlink_seqnum); - if (!urb) { - pr_info("the urb (seqnum %lu) was already given back\n", - unlink->unlink_seqnum); - list_del(&unlink->list); - kfree(unlink); - continue; - } - - urb->status = -ENODEV; - - usb_hcd_unlink_urb_from_ep(hcd, urb); - - list_del(&unlink->list); - - spin_unlock(&vdev->priv_lock); - spin_unlock_irqrestore(&vhci->lock, flags); - - usb_hcd_giveback_urb(hcd, urb, urb->status); - - spin_lock_irqsave(&vhci->lock, flags); - spin_lock(&vdev->priv_lock); - - kfree(unlink); - } - spin_unlock(&vdev->priv_lock); spin_unlock_irqrestore(&vhci->lock, flags); } +static void vhci_device_unlink_cleanup(struct vhci_device *vdev) +{ + /* give back URB of unsent unlink request */ + vhci_cleanup_unlink_list(vdev, &vdev->unlink_tx); + + /* give back URB of unanswered unlink request */ + vhci_cleanup_unlink_list(vdev, &vdev->unlink_rx); +} + /* * The important thing is that only one context begins cleanup. * This is why error handling and cleanup become simple. -- cgit v1.2.3 From 66cce9e73ec61967ed1f97f30cee79bd9a2bb7ee Mon Sep 17 00:00:00 2001 From: Shuah Khan Date: Thu, 19 Aug 2021 16:59:37 -0600 Subject: usbip:vhci_hcd USB port can get stuck in the disabled state When a remote usb device is attached to the local Virtual USB Host Controller Root Hub port, the bound device driver may send a port reset command. vhci_hcd accepts port resets only when the device doesn't have port address assigned to it. When reset happens device is in assigned/used state and vhci_hcd rejects it leaving the port in a stuck state. This problem was found when a blue-tooth or xbox wireless dongle was passed through using usbip. A few drivers reset the port during probe including mt76 driver specific to this bug report. Fix the problem with a change to honor reset requests when device is in used state (VDEV_ST_USED). Reported-and-tested-by: Michael Suggested-by: Michael Signed-off-by: Shuah Khan Link: https://lore.kernel.org/r/20210819225937.41037-1-skhan@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/vhci_hcd.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index b5b31a1d40b6..233265550fc6 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -455,8 +455,14 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, vhci_hcd->port_status[rhport] &= ~(1 << USB_PORT_FEAT_RESET); vhci_hcd->re_timeout = 0; + /* + * A few drivers do usb reset during probe when + * the device could be in VDEV_ST_USED state + */ if (vhci_hcd->vdev[rhport].ud.status == - VDEV_ST_NOTASSIGNED) { + VDEV_ST_NOTASSIGNED || + vhci_hcd->vdev[rhport].ud.status == + VDEV_ST_USED) { usbip_dbg_vhci_rh( " enable rhport %d (status %u)\n", rhport, -- cgit v1.2.3 From 9fe3c93f9de702fa764351201c574a9d0769fab3 Mon Sep 17 00:00:00 2001 From: Wei Ming Chen Date: Sat, 21 Aug 2021 22:26:47 +0800 Subject: usb: gadget: Add description for module parameter The description for "qlen" is missing, and there is a description for this parameter in "Documentation/usb/gadget_printer.rst" Signed-off-by: Wei Ming Chen Link: https://lore.kernel.org/r/20210821142647.2904-1-jj251510319013@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/legacy/printer.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/gadget/legacy/printer.c b/drivers/usb/gadget/legacy/printer.c index 2cd389575084..ed762ba9b629 100644 --- a/drivers/usb/gadget/legacy/printer.c +++ b/drivers/usb/gadget/legacy/printer.c @@ -50,6 +50,7 @@ MODULE_PARM_DESC(iPNPstring, "MFG:linux;MDL:g_printer;CLS:PRINTER;SN:1;"); /* Number of requests to allocate per endpoint, not used for ep0. */ static unsigned qlen = 10; module_param(qlen, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(qlen, "The number of 8k buffers to use per endpoint"); #define QLEN qlen -- cgit v1.2.3 From 8472896f39cfab2d8fec9ca746070aaf02609169 Mon Sep 17 00:00:00 2001 From: Rui Miguel Silva Date: Thu, 19 Aug 2021 19:09:25 +0100 Subject: usb: isp1760: ignore return value for bus change pattern We do not care about the return value of that read between the scratch register write and read, we really just want to make sure that the pattern in the bus get changed to make sure we are testing correctly the scratch pattern. Clang-analyzer complains about the never read scratch variable: >> drivers/usb/isp1760/isp1760-hcd.c:735:2: warning: Value stored to 'scratch' is never read [clang-analyzer-deadcode.DeadStores] scratch = isp1760_hcd_read(hcd, HC_CHIP_ID_HIGH); Just ignore the return value of that CHIP_ID_HIGH read, add more information to the comment above why we are doing this. And as at it, just do a small format change in the error message bellow. Signed-off-by: Rui Miguel Silva Link: https://lore.kernel.org/r/20210819180929.1327349-2-rui.silva@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/isp1760/isp1760-hcd.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c index 825be736be33..2a21fe5aa7a8 100644 --- a/drivers/usb/isp1760/isp1760-hcd.c +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -731,11 +731,15 @@ static int isp1760_hc_setup(struct usb_hcd *hcd) isp1760_hcd_write(hcd, HC_SCRATCH, pattern); - /* Change bus pattern */ - scratch = isp1760_hcd_read(hcd, HC_CHIP_ID_HIGH); + /* + * we do not care about the read value here we just want to + * change bus pattern. + */ + isp1760_hcd_read(hcd, HC_CHIP_ID_HIGH); scratch = isp1760_hcd_read(hcd, HC_SCRATCH); if (scratch != pattern) { - dev_err(hcd->self.controller, "Scratch test failed. 0x%08x\n", scratch); + dev_err(hcd->self.controller, "Scratch test failed. 0x%08x\n", + scratch); return -ENODEV; } -- cgit v1.2.3 From 8e58b7710d6634ed46ae26fedb8459f84f08fd51 Mon Sep 17 00:00:00 2001 From: Rui Miguel Silva Date: Thu, 19 Aug 2021 19:09:26 +0100 Subject: usb: isp1760: check maxpacketsize before using it When checking if we need one more packet on a bulk pipe we may, even though not probable at all, get there with a zero maxpacketsize. In that case for sure no packet, no even a one more, will be allocated. This will clean the clang-analyzer warning: drivers/usb/isp1760/isp1760-hcd.c:1856:38: warning: Division by zero [clang-analyzer-core.DivideZero] && !(urb->transfer_buffer_length % Signed-off-by: Rui Miguel Silva Link: https://lore.kernel.org/r/20210819180929.1327349-3-rui.silva@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/isp1760/isp1760-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c index 2a21fe5aa7a8..5c947a1eae49 100644 --- a/drivers/usb/isp1760/isp1760-hcd.c +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -1854,7 +1854,7 @@ static void packetize_urb(struct usb_hcd *hcd, packet_type = OUT_PID; else packet_type = IN_PID; - } else if (usb_pipebulk(urb->pipe) + } else if (usb_pipebulk(urb->pipe) && maxpacketsize && (urb->transfer_flags & URB_ZERO_PACKET) && !(urb->transfer_buffer_length % maxpacketsize)) { -- cgit v1.2.3 From 5e4cd1b6556302fe6a457e525c256cbef3563543 Mon Sep 17 00:00:00 2001 From: Rui Miguel Silva Date: Thu, 19 Aug 2021 19:09:27 +0100 Subject: usb: isp1760: do not reset retval We do not really need to reset retval before get used bellow. This will avoid the clang-analyzer warning: drivers/usb/isp1760/isp1760-hcd.c:1919:2: warning: Value stored to 'retval' is never read [clang-analyzer-deadcode.DeadStores] retval = 0; Signed-off-by: Rui Miguel Silva Link: https://lore.kernel.org/r/20210819180929.1327349-4-rui.silva@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/isp1760/isp1760-hcd.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c index 5c947a1eae49..aed2714ce0cf 100644 --- a/drivers/usb/isp1760/isp1760-hcd.c +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -1919,7 +1919,6 @@ static int isp1760_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, if (list_empty(&new_qtds)) return -ENOMEM; - retval = 0; spin_lock_irqsave(&priv->lock, spinflags); if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { -- cgit v1.2.3 From 7d1d3882fd9da1ee42fe3ad3a5ffd41fb8204380 Mon Sep 17 00:00:00 2001 From: Rui Miguel Silva Date: Thu, 19 Aug 2021 19:09:28 +0100 Subject: usb: isp1760: do not shift in uninitialized slot Even though it is not expected, and would trigger a WARN_ON, killing a transfer in a uninitialized slot this sequence is warned by clang analyzer, twice: drivers/usb/isp1760/isp1760-hcd.c:1976:18: warning: The result of the left shift is undefined because the right operand is negative [clang-analyzer-core.UndefinedBinaryOperatorResult] skip_map |= (1 << qh->slot); drivers/usb/isp1760/isp1760-hcd.c:1983:18: warning: The result of the left shift is undefined because the right operand is negative [clang-analyzer-core.UndefinedBinaryOperatorResult] skip_map |= (1 << qh->slot); Only set skip map if slot is active. Signed-off-by: Rui Miguel Silva Link: https://lore.kernel.org/r/20210819180929.1327349-5-rui.silva@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/isp1760/isp1760-hcd.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c index aed2714ce0cf..bf8ab3fe2e5a 100644 --- a/drivers/usb/isp1760/isp1760-hcd.c +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -1974,16 +1974,20 @@ static void kill_transfer(struct usb_hcd *hcd, struct urb *urb, /* We need to forcefully reclaim the slot since some transfers never return, e.g. interrupt transfers and NAKed bulk transfers. */ if (usb_pipecontrol(urb->pipe) || usb_pipebulk(urb->pipe)) { - skip_map = isp1760_hcd_read(hcd, HC_ATL_PTD_SKIPMAP); - skip_map |= (1 << qh->slot); - isp1760_hcd_write(hcd, HC_ATL_PTD_SKIPMAP, skip_map); - ndelay(100); + if (qh->slot != -1) { + skip_map = isp1760_hcd_read(hcd, HC_ATL_PTD_SKIPMAP); + skip_map |= (1 << qh->slot); + isp1760_hcd_write(hcd, HC_ATL_PTD_SKIPMAP, skip_map); + ndelay(100); + } priv->atl_slots[qh->slot].qh = NULL; priv->atl_slots[qh->slot].qtd = NULL; } else { - skip_map = isp1760_hcd_read(hcd, HC_INT_PTD_SKIPMAP); - skip_map |= (1 << qh->slot); - isp1760_hcd_write(hcd, HC_INT_PTD_SKIPMAP, skip_map); + if (qh->slot != -1) { + skip_map = isp1760_hcd_read(hcd, HC_INT_PTD_SKIPMAP); + skip_map |= (1 << qh->slot); + isp1760_hcd_write(hcd, HC_INT_PTD_SKIPMAP, skip_map); + } priv->int_slots[qh->slot].qh = NULL; priv->int_slots[qh->slot].qtd = NULL; } -- cgit v1.2.3 From de940244e8987a76d73fb2b0057ecd494cbfeefd Mon Sep 17 00:00:00 2001 From: Rui Miguel Silva Date: Thu, 19 Aug 2021 19:09:29 +0100 Subject: usb: isp1760: clean never read udc_enabled warning When CONFIG_USB_ISP1761_UDC is not enabled the udc_enabled variable is never used since it is short circuited before in the logic operations. This would trigger the following warning by clang analyzer: drivers/usb/isp1760/isp1760-core.c:490:2: warning: Value stored to 'udc_enabled' is never read [clang-analyzer-deadcode.DeadStores] udc_enabled = ((devflags & ISP1760_FLAG_ISP1763) || ^ drivers/usb/isp1760/isp1760-core.c:490:2: note: Value stored to 'udc_enabled' is never read Just swap the other of the operands in the logic operations. Signed-off-by: Rui Miguel Silva Link: https://lore.kernel.org/r/20210819180929.1327349-6-rui.silva@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/isp1760/isp1760-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/isp1760/isp1760-core.c b/drivers/usb/isp1760/isp1760-core.c index ff07e2890692..cb70f9d63cdd 100644 --- a/drivers/usb/isp1760/isp1760-core.c +++ b/drivers/usb/isp1760/isp1760-core.c @@ -491,7 +491,7 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, (devflags & ISP1760_FLAG_ISP1761)); if ((!IS_ENABLED(CONFIG_USB_ISP1760_HCD) || usb_disabled()) && - (!IS_ENABLED(CONFIG_USB_ISP1761_UDC) || !udc_enabled)) + (!udc_enabled || !IS_ENABLED(CONFIG_USB_ISP1761_UDC))) return -ENODEV; isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); @@ -571,7 +571,7 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, return ret; } - if (IS_ENABLED(CONFIG_USB_ISP1761_UDC) && udc_enabled) { + if (udc_enabled && IS_ENABLED(CONFIG_USB_ISP1761_UDC)) { ret = isp1760_udc_register(isp, irq, irqflags); if (ret < 0) { isp1760_hcd_unregister(hcd); -- cgit v1.2.3 From 76d55a633ab61e70cb56720830e7be3dec0842fe Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 20 Aug 2021 14:59:12 +0800 Subject: Revert "usb: xhci-mtk: relax TT periodic bandwidth allocation" As discussed in following patch: https://patchwork.kernel.org/patch/12420339 No need calculate number of uframes again, but should use value form check_sch_tt(), if we plan to remove extra CS, also can do it in check_sch_tt(). So revert this patch, and prepare to send new patch for it. This reverts commit 548011957d1d72e0b662300c8b32b81d593b796e. Cc: Ikjoon Jang Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20210820065913.64490-1-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mtk-sch.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c index 46cbf5d54f4f..f9b4d27ce449 100644 --- a/drivers/usb/host/xhci-mtk-sch.c +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -459,17 +459,16 @@ static int check_fs_bus_bw(struct mu3h_sch_ep_info *sch_ep, int offset) u32 num_esit, tmp; int base; int i, j; - u8 uframes = DIV_ROUND_UP(sch_ep->maxpkt, FS_PAYLOAD_MAX); num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit; - - if (sch_ep->ep_type == INT_IN_EP || sch_ep->ep_type == ISOC_IN_EP) - offset++; - for (i = 0; i < num_esit; i++) { base = offset + i * sch_ep->esit; - for (j = 0; j < uframes; j++) { + /* + * Compared with hs bus, no matter what ep type, + * the hub will always delay one uframe to send data + */ + for (j = 0; j < sch_ep->cs_count; j++) { tmp = tt->fs_bus_bw[base + j] + sch_ep->bw_cost_per_microframe; if (tmp > FS_PAYLOAD_MAX) return -ESCH_BW_OVERFLOW; @@ -547,8 +546,6 @@ static void update_sch_tt(struct mu3h_sch_ep_info *sch_ep, bool used) u32 base, num_esit; int bw_updated; int i, j; - int offset = sch_ep->offset; - u8 uframes = DIV_ROUND_UP(sch_ep->maxpkt, FS_PAYLOAD_MAX); num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit; @@ -557,13 +554,10 @@ static void update_sch_tt(struct mu3h_sch_ep_info *sch_ep, bool used) else bw_updated = -sch_ep->bw_cost_per_microframe; - if (sch_ep->ep_type == INT_IN_EP || sch_ep->ep_type == ISOC_IN_EP) - offset++; - for (i = 0; i < num_esit; i++) { - base = offset + i * sch_ep->esit; + base = sch_ep->offset + i * sch_ep->esit; - for (j = 0; j < uframes; j++) + for (j = 0; j < sch_ep->cs_count; j++) tt->fs_bus_bw[base + j] += bw_updated; } -- cgit v1.2.3 From f2a9797b4efe54c94cc5ceb82ce1a4fba8b70a51 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 20 Aug 2021 14:59:13 +0800 Subject: Revert "usb: xhci-mtk: Do not use xhci's virt_dev in drop_endpoint" I find the patch introduce some issues, e.g. 1. oops happens when xhci_gen_setup() failed, and hash is not init but try to destroy it; 2. memory leakage happens when fail to insert ep, need free sch_ep, or insert ep after insert int list; 3. memory leakage happens when fail to allocate sch_array, need destroy rhashtable; 4. it's better to check ep->hcpriv when drop ep; so prefer to revert this patch, and resend it after the issues are fixed. This reverts commit b8731209958a1dffccc2888121f4c0280c990550. Cc: Ikjoon Jang Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20210820065913.64490-2-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mtk-sch.c | 140 ++++++++++++++++++---------------------- drivers/usb/host/xhci-mtk.h | 15 ++--- 2 files changed, 69 insertions(+), 86 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c index f9b4d27ce449..cffcaf4dfa9f 100644 --- a/drivers/usb/host/xhci-mtk-sch.c +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -80,7 +80,7 @@ decode_ep(struct usb_host_endpoint *ep, enum usb_device_speed speed) interval /= 1000; } - snprintf(buf, DBG_BUF_EN, "%s ep%d%s %s, mpkt:%d, interval:%d/%d%s", + snprintf(buf, DBG_BUF_EN, "%s ep%d%s %s, mpkt:%d, interval:%d/%d%s\n", usb_speed_string(speed), usb_endpoint_num(epd), usb_endpoint_dir_in(epd) ? "in" : "out", usb_ep_type_string(usb_endpoint_type(epd)), @@ -129,12 +129,6 @@ get_bw_info(struct xhci_hcd_mtk *mtk, struct usb_device *udev, int bw_index; virt_dev = xhci->devs[udev->slot_id]; - if (WARN_ONCE(!virt_dev, "xhci-mtk: usb %s has no virt_dev\n", - dev_name(&udev->dev))) - return NULL; - if (WARN_ONCE(!virt_dev->real_port, "xhci-mtk: usb %s has invalid port number\n", - dev_name(&udev->dev))) - return NULL; if (udev->speed >= USB_SPEED_SUPER) { if (usb_endpoint_dir_out(&ep->desc)) @@ -200,6 +194,7 @@ static struct mu3h_sch_tt *find_tt(struct usb_device *udev) } return ERR_PTR(-ENOMEM); } + INIT_LIST_HEAD(&tt->ep_list); *ptt = tt; } @@ -229,7 +224,7 @@ static void drop_tt(struct usb_device *udev) } tt = *ptt; - if (!tt || tt->nr_eps > 0) + if (!tt || !list_empty(&tt->ep_list)) return; /* never allocated , or still in use*/ *ptt = NULL; @@ -241,19 +236,13 @@ static void drop_tt(struct usb_device *udev) } } -static struct mu3h_sch_ep_info *create_sch_ep(struct xhci_hcd_mtk *mtk, - struct usb_device *udev, struct usb_host_endpoint *ep, - struct xhci_ep_ctx *ep_ctx) +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; - struct mu3h_sch_bw_info *bw_info; - - bw_info = get_bw_info(mtk, udev, ep); - if (!bw_info) - return ERR_PTR(-ENODEV); if (is_fs_or_ls(udev->speed)) len_bw_budget_table = TT_MICROFRAMES_MAX; @@ -277,10 +266,11 @@ static struct mu3h_sch_ep_info *create_sch_ep(struct xhci_hcd_mtk *mtk, } } - sch_ep->bw_info = bw_info; sch_ep->sch_tt = tt; sch_ep->ep = ep; sch_ep->speed = udev->speed; + INIT_LIST_HEAD(&sch_ep->endpoint); + INIT_LIST_HEAD(&sch_ep->tt_endpoint); return sch_ep; } @@ -562,9 +552,9 @@ static void update_sch_tt(struct mu3h_sch_ep_info *sch_ep, bool used) } if (used) - tt->nr_eps++; - else if (!WARN_ONCE(tt->nr_eps < 1, "unbalanced sch_tt's ep count")) - tt->nr_eps--; + list_add_tail(&sch_ep->tt_endpoint, &tt->ep_list); + else + list_del(&sch_ep->tt_endpoint); } static int load_ep_bw(struct mu3h_sch_bw_info *sch_bw, @@ -595,9 +585,9 @@ static u32 get_esit_boundary(struct mu3h_sch_ep_info *sch_ep) return boundary; } -static int check_sch_bw(struct mu3h_sch_ep_info *sch_ep) +static int check_sch_bw(struct mu3h_sch_bw_info *sch_bw, + struct mu3h_sch_ep_info *sch_ep) { - struct mu3h_sch_bw_info *sch_bw = sch_ep->bw_info; const u32 esit_boundary = get_esit_boundary(sch_ep); const u32 bw_boundary = get_bw_boundary(sch_ep->speed); u32 offset; @@ -643,33 +633,23 @@ static int check_sch_bw(struct mu3h_sch_ep_info *sch_ep) return load_ep_bw(sch_bw, sch_ep, true); } -static const struct rhashtable_params sch_ep_table_param = { - .key_len = sizeof(struct usb_host_endpoint *), - .key_offset = offsetof(struct mu3h_sch_ep_info, ep), - .head_offset = offsetof(struct mu3h_sch_ep_info, ep_link), -}; - -static void destroy_sch_ep(struct xhci_hcd_mtk *mtk, struct usb_device *udev, - struct mu3h_sch_ep_info *sch_ep) +static void destroy_sch_ep(struct usb_device *udev, + struct mu3h_sch_bw_info *sch_bw, struct mu3h_sch_ep_info *sch_ep) { /* only release ep bw check passed by check_sch_bw() */ if (sch_ep->allocated) - load_ep_bw(sch_ep->bw_info, sch_ep, false); + load_ep_bw(sch_bw, sch_ep, false); if (sch_ep->sch_tt) drop_tt(udev); list_del(&sch_ep->endpoint); - rhashtable_remove_fast(&mtk->sch_ep_table, &sch_ep->ep_link, - sch_ep_table_param); kfree(sch_ep); } -static bool need_bw_sch(struct usb_device *udev, - struct usb_host_endpoint *ep) +static bool need_bw_sch(struct usb_host_endpoint *ep, + enum usb_device_speed speed, int has_tt) { - bool has_tt = udev->tt && udev->tt->hub->parent; - /* only for periodic endpoints */ if (usb_endpoint_xfer_control(&ep->desc) || usb_endpoint_xfer_bulk(&ep->desc)) @@ -680,7 +660,7 @@ static bool need_bw_sch(struct usb_device *udev, * a TT are also ignored, root-hub will schedule them directly, * but need set @bpkts field of endpoint context to 1. */ - if (is_fs_or_ls(udev->speed) && !has_tt) + if (is_fs_or_ls(speed) && !has_tt) return false; /* skip endpoint with zero maxpkt */ @@ -695,12 +675,7 @@ int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk) struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd); struct mu3h_sch_bw_info *sch_array; int num_usb_bus; - int ret; - - /* mu3h_sch_ep_info table, 'usb_host_endpoint*' as a key */ - ret = rhashtable_init(&mtk->sch_ep_table, &sch_ep_table_param); - if (ret) - return ret; + int i; /* ss IN and OUT are separated */ num_usb_bus = xhci->usb3_rhub.num_ports * 2 + xhci->usb2_rhub.num_ports; @@ -709,6 +684,9 @@ int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk) if (sch_array == NULL) return -ENOMEM; + for (i = 0; i < num_usb_bus; i++) + INIT_LIST_HEAD(&sch_array[i].bw_ep_list); + mtk->sch_array = sch_array; INIT_LIST_HEAD(&mtk->bw_ep_chk_list); @@ -718,7 +696,6 @@ int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk) void xhci_mtk_sch_exit(struct xhci_hcd_mtk *mtk) { - rhashtable_destroy(&mtk->sch_ep_table); kfree(mtk->sch_array); } @@ -736,7 +713,9 @@ static int add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, ep_index = xhci_get_endpoint_index(&ep->desc); ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); - if (!need_bw_sch(udev, ep)) { + xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed)); + + if (!need_bw_sch(ep, udev->speed, !!virt_dev->tt_info)) { /* * set @bpkts to 1 if it is LS or FS periodic endpoint, and its * device does not connected through an external HS hub @@ -748,22 +727,14 @@ static int add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, return 0; } - xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed)); - - sch_ep = create_sch_ep(mtk, udev, ep, ep_ctx); + sch_ep = create_sch_ep(udev, ep, ep_ctx); if (IS_ERR_OR_NULL(sch_ep)) return -ENOMEM; - if (rhashtable_insert_fast(&mtk->sch_ep_table, &sch_ep->ep_link, - sch_ep_table_param)) - return -EEXIST; - setup_sch_info(ep_ctx, sch_ep); list_add_tail(&sch_ep->endpoint, &mtk->bw_ep_chk_list); - xhci_dbg(xhci, "added sch_ep %p : %p\n", sch_ep, ep); - return 0; } @@ -772,20 +743,25 @@ static void drop_ep_quirk(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; - void *key = ep; + struct xhci_virt_device *virt_dev; + struct mu3h_sch_bw_info *sch_bw; + struct mu3h_sch_ep_info *sch_ep, *tmp; - if (!need_bw_sch(udev, ep)) - return; + virt_dev = xhci->devs[udev->slot_id]; xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed)); - sch_ep = rhashtable_lookup_fast(&mtk->sch_ep_table, &key, - sch_ep_table_param); - if (sch_ep) - destroy_sch_ep(mtk, udev, sch_ep); - else - xhci_dbg(xhci, "ep %p is not on the bw table\n", ep); + if (!need_bw_sch(ep, udev->speed, !!virt_dev->tt_info)) + return; + + 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) { + destroy_sch_ep(udev, sch_bw, sch_ep); + break; + } + } } int xhci_mtk_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) @@ -793,22 +769,30 @@ 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 xhci_virt_device *virt_dev = xhci->devs[udev->slot_id]; - struct mu3h_sch_ep_info *sch_ep; + struct mu3h_sch_bw_info *sch_bw; + struct mu3h_sch_ep_info *sch_ep, *tmp; int ret; xhci_dbg(xhci, "%s() udev %s\n", __func__, dev_name(&udev->dev)); list_for_each_entry(sch_ep, &mtk->bw_ep_chk_list, endpoint) { - struct xhci_ep_ctx *ep_ctx; - struct usb_host_endpoint *ep = sch_ep->ep; - unsigned int ep_index = xhci_get_endpoint_index(&ep->desc); + sch_bw = get_bw_info(mtk, udev, sch_ep->ep); - ret = check_sch_bw(sch_ep); + ret = check_sch_bw(sch_bw, sch_ep); if (ret) { xhci_err(xhci, "Not enough bandwidth! (%s)\n", sch_error_string(-ret)); return -ENOSPC; } + } + + list_for_each_entry_safe(sch_ep, tmp, &mtk->bw_ep_chk_list, endpoint) { + struct xhci_ep_ctx *ep_ctx; + struct usb_host_endpoint *ep = sch_ep->ep; + unsigned int ep_index = xhci_get_endpoint_index(&ep->desc); + + sch_bw = get_bw_info(mtk, udev, ep); + list_move_tail(&sch_ep->endpoint, &sch_bw->bw_ep_list); ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); ep_ctx->reserved[0] = cpu_to_le32(EP_BPKTS(sch_ep->pkts) @@ -822,23 +806,22 @@ int xhci_mtk_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) sch_ep->offset, sch_ep->repeat); } - ret = xhci_check_bandwidth(hcd, udev); - if (!ret) - INIT_LIST_HEAD(&mtk->bw_ep_chk_list); - - return ret; + return xhci_check_bandwidth(hcd, udev); } void xhci_mtk_reset_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_bw_info *sch_bw; struct mu3h_sch_ep_info *sch_ep, *tmp; xhci_dbg(xhci, "%s() udev %s\n", __func__, dev_name(&udev->dev)); - list_for_each_entry_safe(sch_ep, tmp, &mtk->bw_ep_chk_list, endpoint) - destroy_sch_ep(mtk, udev, sch_ep); + list_for_each_entry_safe(sch_ep, tmp, &mtk->bw_ep_chk_list, endpoint) { + sch_bw = get_bw_info(mtk, udev, sch_ep->ep); + destroy_sch_ep(udev, sch_bw, sch_ep); + } xhci_reset_bandwidth(hcd, udev); } @@ -867,7 +850,8 @@ int xhci_mtk_drop_ep(struct usb_hcd *hcd, struct usb_device *udev, if (ret) return ret; - drop_ep_quirk(hcd, udev, ep); + if (ep->hcpriv) + drop_ep_quirk(hcd, udev, ep); return 0; } diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h index ddcf25524f67..ace432356c41 100644 --- a/drivers/usb/host/xhci-mtk.h +++ b/drivers/usb/host/xhci-mtk.h @@ -10,7 +10,6 @@ #define _XHCI_MTK_H_ #include -#include #include "xhci.h" @@ -26,34 +25,36 @@ /** * @fs_bus_bw: array to keep track of bandwidth already used for FS - * @nr_eps: number of endpoints using this TT + * @ep_list: Endpoints using this TT */ struct mu3h_sch_tt { u32 fs_bus_bw[XHCI_MTK_MAX_ESIT]; - int nr_eps; + struct list_head ep_list; }; /** * struct mu3h_sch_bw_info: schedule information for bandwidth domain * * @bus_bw: array to keep track of bandwidth already used at each uframes + * @bw_ep_list: eps in the bandwidth domain * * treat a HS root port as a bandwidth domain, but treat a SS root port as * two bandwidth domains, one for IN eps and another for OUT eps. */ struct mu3h_sch_bw_info { u32 bus_bw[XHCI_MTK_MAX_ESIT]; + struct list_head bw_ep_list; }; /** * struct mu3h_sch_ep_info: schedule information for endpoint * - * @bw_info: bandwidth domain which this endpoint belongs * @esit: unit is 125us, equal to 2 << Interval field in ep-context * @num_budget_microframes: number of continuous uframes * (@repeat==1) scheduled within the interval * @bw_cost_per_microframe: bandwidth cost per microframe - * @endpoint: linked into bw_ep_chk_list, used by check_bandwidth hook + * @endpoint: linked into bandwidth domain which it belongs to + * @tt_endpoint: linked into mu3h_sch_tt's list which it belongs to * @sch_tt: mu3h_sch_tt linked into * @ep_type: endpoint type * @maxpkt: max packet size of endpoint @@ -81,12 +82,11 @@ struct mu3h_sch_ep_info { u32 num_budget_microframes; u32 bw_cost_per_microframe; struct list_head endpoint; - struct mu3h_sch_bw_info *bw_info; + struct list_head tt_endpoint; struct mu3h_sch_tt *sch_tt; u32 ep_type; u32 maxpkt; struct usb_host_endpoint *ep; - struct rhash_head ep_link; enum usb_device_speed speed; bool allocated; /* @@ -134,7 +134,6 @@ struct xhci_hcd_mtk { struct device *dev; struct usb_hcd *hcd; struct mu3h_sch_bw_info *sch_array; - struct rhashtable sch_ep_table; struct list_head bw_ep_chk_list; struct mu3c_ippc_regs __iomem *ippc_regs; int num_u2_ports; -- cgit v1.2.3 From d2f42e09393c774ab79088d8e3afcc62b3328fc9 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Wed, 18 Aug 2021 21:32:38 +0200 Subject: usb: bdc: Fix an error handling path in 'bdc_probe()' when no suitable DMA config is available If no suitable DMA configuration is available, a previous 'bdc_phy_init()' call must be undone by a corresponding 'bdc_phy_exit()' call. Branch to the existing error handling path instead of returning directly. Fixes: cc29d4f67757 ("usb: bdc: Add support for USB phy") Acked-by: Florian Fainelli Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/0c5910979f39225d5d8fe68c9ab1c147c68ddee1.1629314734.git.christophe.jaillet@wanadoo.fr Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc_core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/bdc/bdc_core.c b/drivers/usb/gadget/udc/bdc/bdc_core.c index 0bef6b3f049b..251db57e51fa 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_core.c +++ b/drivers/usb/gadget/udc/bdc/bdc_core.c @@ -560,7 +560,8 @@ static int bdc_probe(struct platform_device *pdev) if (ret) { dev_err(dev, "No suitable DMA config available, abort\n"); - return -ENOTSUPP; + ret = -ENOTSUPP; + goto phycleanup; } dev_dbg(dev, "Using 32-bit address\n"); } -- cgit v1.2.3 From 6f15a2a09cecb7a2faba4a75bbd101f6f962294b Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Wed, 18 Aug 2021 21:32:49 +0200 Subject: usb: bdc: Fix a resource leak in the error handling path of 'bdc_probe()' If an error occurs after a successful 'clk_prepare_enable()' call, it must be undone by a corresponding 'clk_disable_unprepare()' call. This call is already present in the remove function. Add this call in the error handling path and reorder the code so that the 'clk_prepare_enable()' call happens later in the function. The goal is to have as much managed resources functions as possible before the 'clk_prepare_enable()' call in order to keep the error handling path simple. While at it, remove the now unneeded 'clk' variable. Fixes: c87dca047849 ("usb: bdc: Add clock enable for new chips with a separate BDC clock") Acked-by: Florian Fainelli Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/f8a4a6897deb0c8cb2e576580790303550f15fcd.1629314734.git.christophe.jaillet@wanadoo.fr Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc_core.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/gadget/udc/bdc/bdc_core.c b/drivers/usb/gadget/udc/bdc/bdc_core.c index 251db57e51fa..fa1a3908ec3b 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_core.c +++ b/drivers/usb/gadget/udc/bdc/bdc_core.c @@ -488,27 +488,14 @@ static int bdc_probe(struct platform_device *pdev) int irq; u32 temp; struct device *dev = &pdev->dev; - struct clk *clk; int phy_num; dev_dbg(dev, "%s()\n", __func__); - clk = devm_clk_get_optional(dev, "sw_usbd"); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - ret = clk_prepare_enable(clk); - if (ret) { - dev_err(dev, "could not enable clock\n"); - return ret; - } - bdc = devm_kzalloc(dev, sizeof(*bdc), GFP_KERNEL); if (!bdc) return -ENOMEM; - bdc->clk = clk; - bdc->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(bdc->regs)) return PTR_ERR(bdc->regs); @@ -545,10 +532,20 @@ static int bdc_probe(struct platform_device *pdev) } } + bdc->clk = devm_clk_get_optional(dev, "sw_usbd"); + if (IS_ERR(bdc->clk)) + return PTR_ERR(bdc->clk); + + ret = clk_prepare_enable(bdc->clk); + if (ret) { + dev_err(dev, "could not enable clock\n"); + return ret; + } + ret = bdc_phy_init(bdc); if (ret) { dev_err(bdc->dev, "BDC phy init failure:%d\n", ret); - return ret; + goto disable_clk; } temp = bdc_readl(bdc->regs, BDC_BDCCAP1); @@ -581,6 +578,8 @@ cleanup: bdc_hw_exit(bdc); phycleanup: bdc_phy_exit(bdc); +disable_clk: + clk_disable_unprepare(bdc->clk); return ret; } -- cgit v1.2.3 From 7f85c16f40d8be5656fb3476909db5c3a5a9c6ea Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Tue, 17 Aug 2021 16:36:23 +0800 Subject: usb: xhci-mtk: fix use-after-free of mtk->hcd BUG: KASAN: use-after-free in usb_hcd_is_primary_hcd+0x38/0x60 Call trace: dump_backtrace+0x0/0x3dc show_stack+0x20/0x2c dump_stack+0x15c/0x1d4 print_address_description+0x7c/0x510 kasan_report+0x164/0x1ac __asan_report_load8_noabort+0x44/0x50 usb_hcd_is_primary_hcd+0x38/0x60 xhci_mtk_runtime_suspend+0x68/0x148 pm_generic_runtime_suspend+0x90/0xac __rpm_callback+0xb8/0x1f4 rpm_callback+0x54/0x1d0 rpm_suspend+0x4e0/0xc84 __pm_runtime_suspend+0xc4/0x114 xhci_mtk_probe+0xa58/0xd00 This may happen when probe fails, needn't suspend it synchronously, fix it by using pm_runtime_put_noidle(). Reported-by: Pi Hsun Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1629189389-18779-3-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mtk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c index 2548976bcf05..cb27569186a0 100644 --- a/drivers/usb/host/xhci-mtk.c +++ b/drivers/usb/host/xhci-mtk.c @@ -569,7 +569,7 @@ disable_ldos: xhci_mtk_ldos_disable(mtk); disable_pm: - pm_runtime_put_sync_autosuspend(dev); + pm_runtime_put_noidle(dev); pm_runtime_disable(dev); return ret; } -- cgit v1.2.3 From 7465d7b66ac73db87b9eb99be01500093f80b575 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Tue, 17 Aug 2021 16:36:24 +0800 Subject: usb: xhci-mtk: support option to disable usb2 ports Add support to disable specific usb2 host ports, it's useful when a usb2 port is disabled on some platforms, but enabled on others for the same SoC. Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1629189389-18779-4-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mtk.c | 12 ++++++++++-- drivers/usb/host/xhci-mtk.h | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c index cb27569186a0..8f27edac9ee3 100644 --- a/drivers/usb/host/xhci-mtk.c +++ b/drivers/usb/host/xhci-mtk.c @@ -115,8 +115,11 @@ static int xhci_mtk_host_enable(struct xhci_hcd_mtk *mtk) writel(value, &ippc->u3_ctrl_p[i]); } - /* power on and enable all u2 ports */ + /* power on and enable all u2 ports except skipped ones */ for (i = 0; i < mtk->num_u2_ports; i++) { + if (BIT(i) & mtk->u2p_dis_msk) + continue; + value = readl(&ippc->u2_ctrl_p[i]); value &= ~(CTRL_U2_PORT_PDN | CTRL_U2_PORT_DIS); value |= CTRL_U2_PORT_HOST_SEL; @@ -163,8 +166,11 @@ static int xhci_mtk_host_disable(struct xhci_hcd_mtk *mtk) writel(value, &ippc->u3_ctrl_p[i]); } - /* power down all u2 ports */ + /* power down all u2 ports except skipped ones */ for (i = 0; i < mtk->num_u2_ports; i++) { + if (BIT(i) & mtk->u2p_dis_msk) + continue; + value = readl(&ippc->u2_ctrl_p[i]); value |= CTRL_U2_PORT_PDN; writel(value, &ippc->u2_ctrl_p[i]); @@ -444,6 +450,8 @@ static int xhci_mtk_probe(struct platform_device *pdev) /* optional property, ignore the error if it does not exist */ of_property_read_u32(node, "mediatek,u3p-dis-msk", &mtk->u3p_dis_msk); + of_property_read_u32(node, "mediatek,u2p-dis-msk", + &mtk->u2p_dis_msk); ret = usb_wakeup_of_property_parse(mtk, node); if (ret) { diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h index ace432356c41..0466bc8f7500 100644 --- a/drivers/usb/host/xhci-mtk.h +++ b/drivers/usb/host/xhci-mtk.h @@ -138,6 +138,7 @@ struct xhci_hcd_mtk { struct mu3c_ippc_regs __iomem *ippc_regs; int num_u2_ports; int num_u3_ports; + int u2p_dis_msk; int u3p_dis_msk; struct regulator *vusb33; struct regulator *vbus; -- cgit v1.2.3 From de5107f473190538a65aac7edea85209cd5c1a8f Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Tue, 17 Aug 2021 16:36:25 +0800 Subject: usb: xhci-mtk: fix issue of out-of-bounds array access Bus bandwidth array access is based on esit, increase one will cause out-of-bounds issue; for example, when esit is XHCI_MTK_MAX_ESIT, will overstep boundary. Fixes: 7c986fbc16ae ("usb: xhci-mtk: get the microframe boundary for ESIT") Cc: Reported-by: Stan Lu Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1629189389-18779-5-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mtk-sch.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c index cffcaf4dfa9f..0bb1a6295d64 100644 --- a/drivers/usb/host/xhci-mtk-sch.c +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -575,10 +575,12 @@ static u32 get_esit_boundary(struct mu3h_sch_ep_info *sch_ep) u32 boundary = sch_ep->esit; if (sch_ep->sch_tt) { /* LS/FS with TT */ - /* tune for CS */ - if (sch_ep->ep_type != ISOC_OUT_EP) - boundary++; - else if (boundary > 1) /* normally esit >= 8 for FS/LS */ + /* + * tune for CS, normally esit >= 8 for FS/LS, + * not add one for other types to avoid access array + * out of boundary + */ + if (sch_ep->ep_type == ISOC_OUT_EP && boundary > 1) boundary--; } -- cgit v1.2.3 From 451d3912586aad3f71f1c97780a1c21e3de98413 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Tue, 17 Aug 2021 16:36:26 +0800 Subject: usb: xhci-mtk: update fs bus bandwidth by bw_budget_table Use @bw_budget_table[] to update fs bus bandwidth due to not all microframes consume @bw_cost_per_microframe, see setup_sch_info(). Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1629189389-18779-6-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mtk-sch.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c index 0bb1a6295d64..10c0f0f6461f 100644 --- a/drivers/usb/host/xhci-mtk-sch.c +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -458,8 +458,8 @@ static int check_fs_bus_bw(struct mu3h_sch_ep_info *sch_ep, int offset) * Compared with hs bus, no matter what ep type, * the hub will always delay one uframe to send data */ - for (j = 0; j < sch_ep->cs_count; j++) { - tmp = tt->fs_bus_bw[base + j] + sch_ep->bw_cost_per_microframe; + for (j = 0; j < sch_ep->num_budget_microframes; j++) { + tmp = tt->fs_bus_bw[base + j] + sch_ep->bw_budget_table[j]; if (tmp > FS_PAYLOAD_MAX) return -ESCH_BW_OVERFLOW; } @@ -534,21 +534,18 @@ static void update_sch_tt(struct mu3h_sch_ep_info *sch_ep, bool used) { struct mu3h_sch_tt *tt = sch_ep->sch_tt; u32 base, num_esit; - int bw_updated; int i, j; num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit; - if (used) - bw_updated = sch_ep->bw_cost_per_microframe; - else - bw_updated = -sch_ep->bw_cost_per_microframe; - for (i = 0; i < num_esit; i++) { base = sch_ep->offset + i * sch_ep->esit; - for (j = 0; j < sch_ep->cs_count; j++) - tt->fs_bus_bw[base + j] += bw_updated; + for (j = 0; j < sch_ep->num_budget_microframes; j++) + if (used) + tt->fs_bus_bw[base + j] += sch_ep->bw_budget_table[j]; + else + tt->fs_bus_bw[base + j] -= sch_ep->bw_budget_table[j]; } if (used) -- cgit v1.2.3 From 614c8c67a071d44be54266b78c21e2a505ac1b32 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Tue, 17 Aug 2021 16:36:27 +0800 Subject: usb: xhci-mtk: check boundary before check tt check_sch_tt() will access fs_bus_bw[] array, check boundary firstly to avoid out-of-bounds issue. Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1629189389-18779-7-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mtk-sch.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c index 10c0f0f6461f..c2f13d69c607 100644 --- a/drivers/usb/host/xhci-mtk-sch.c +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -600,13 +600,14 @@ static int check_sch_bw(struct mu3h_sch_bw_info *sch_bw, * and find a microframe where its worst bandwidth is minimum. */ for (offset = 0; offset < sch_ep->esit; offset++) { - ret = check_sch_tt(sch_ep, offset); - if (ret) - continue; if ((offset + sch_ep->num_budget_microframes) > esit_boundary) break; + ret = check_sch_tt(sch_ep, offset); + if (ret) + continue; + worst_bw = get_max_bw(sch_bw, sch_ep, offset); if (worst_bw > bw_boundary) continue; -- cgit v1.2.3 From 82799c80b46a151abc693b20ffea08bfba14be8e Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Tue, 17 Aug 2021 16:36:28 +0800 Subject: usb: xhci-mtk: add a member of num_esit Add a member num_esit to save the number of esit, then no need caculate it in some functions. Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1629189389-18779-8-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mtk-sch.c | 20 +++++++------------- drivers/usb/host/xhci-mtk.h | 2 ++ 2 files changed, 9 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c index c2f13d69c607..a9fcf7e30c41 100644 --- a/drivers/usb/host/xhci-mtk-sch.c +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -297,6 +297,7 @@ static void setup_sch_info(struct xhci_ep_ctx *ep_ctx, CTX_TO_MAX_ESIT_PAYLOAD(le32_to_cpu(ep_ctx->tx_info)); sch_ep->esit = get_esit(ep_ctx); + sch_ep->num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit; sch_ep->ep_type = ep_type; sch_ep->maxpkt = maxpkt; sch_ep->offset = 0; @@ -401,14 +402,12 @@ static void setup_sch_info(struct xhci_ep_ctx *ep_ctx, static u32 get_max_bw(struct mu3h_sch_bw_info *sch_bw, struct mu3h_sch_ep_info *sch_ep, u32 offset) { - u32 num_esit; u32 max_bw = 0; u32 bw; int i; int j; - num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit; - for (i = 0; i < num_esit; i++) { + for (i = 0; i < sch_ep->num_esit; i++) { u32 base = offset + i * sch_ep->esit; for (j = 0; j < sch_ep->num_budget_microframes; j++) { @@ -424,13 +423,11 @@ static u32 get_max_bw(struct mu3h_sch_bw_info *sch_bw, static void update_bus_bw(struct mu3h_sch_bw_info *sch_bw, struct mu3h_sch_ep_info *sch_ep, bool used) { - u32 num_esit; u32 base; int i; int j; - num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit; - for (i = 0; i < num_esit; i++) { + for (i = 0; i < sch_ep->num_esit; i++) { base = sch_ep->offset + i * sch_ep->esit; for (j = 0; j < sch_ep->num_budget_microframes; j++) { if (used) @@ -446,12 +443,11 @@ static void update_bus_bw(struct mu3h_sch_bw_info *sch_bw, static int check_fs_bus_bw(struct mu3h_sch_ep_info *sch_ep, int offset) { struct mu3h_sch_tt *tt = sch_ep->sch_tt; - u32 num_esit, tmp; + u32 tmp; int base; int i, j; - num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit; - for (i = 0; i < num_esit; i++) { + for (i = 0; i < sch_ep->num_esit; i++) { base = offset + i * sch_ep->esit; /* @@ -533,12 +529,10 @@ static int check_sch_tt(struct mu3h_sch_ep_info *sch_ep, u32 offset) static void update_sch_tt(struct mu3h_sch_ep_info *sch_ep, bool used) { struct mu3h_sch_tt *tt = sch_ep->sch_tt; - u32 base, num_esit; + u32 base; int i, j; - num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit; - - for (i = 0; i < num_esit; i++) { + for (i = 0; i < sch_ep->num_esit; i++) { base = sch_ep->offset + i * sch_ep->esit; for (j = 0; j < sch_ep->num_budget_microframes; j++) diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h index 0466bc8f7500..56dc348349af 100644 --- a/drivers/usb/host/xhci-mtk.h +++ b/drivers/usb/host/xhci-mtk.h @@ -50,6 +50,7 @@ struct mu3h_sch_bw_info { * struct mu3h_sch_ep_info: schedule information for endpoint * * @esit: unit is 125us, equal to 2 << Interval field in ep-context + * @num_esit: number of @esit in a period * @num_budget_microframes: number of continuous uframes * (@repeat==1) scheduled within the interval * @bw_cost_per_microframe: bandwidth cost per microframe @@ -79,6 +80,7 @@ struct mu3h_sch_bw_info { */ struct mu3h_sch_ep_info { u32 esit; + u32 num_esit; u32 num_budget_microframes; u32 bw_cost_per_microframe; struct list_head endpoint; -- cgit v1.2.3 From 926d60ae64a623db3c1afcc524c23709615893d7 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Tue, 17 Aug 2021 16:36:29 +0800 Subject: usb: xhci-mtk: modify the SOF/ITP interval for mt8195 There are 4 USB controllers on MT8195, the controllers (IP1~IP3, exclude IP0) have a wrong default SOF/ITP interval which is calculated from the frame counter clock 24Mhz by default, but in fact, the frame counter clock is 48Mhz, so we should set the accurate interval according to 48Mhz for those controllers. Note: the first controller no need set it. Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/1629189389-18779-9-git-send-email-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mtk.c | 65 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c index 8f27edac9ee3..c53f6f276d5c 100644 --- a/drivers/usb/host/xhci-mtk.c +++ b/drivers/usb/host/xhci-mtk.c @@ -56,6 +56,27 @@ /* u2_phy_pll register */ #define CTRL_U2_FORCE_PLL_STB BIT(28) +/* xHCI CSR */ +#define LS_EOF_CFG 0x930 +#define LSEOF_OFFSET 0x89 + +#define FS_EOF_CFG 0x934 +#define FSEOF_OFFSET 0x2e + +#define SS_GEN1_EOF_CFG 0x93c +#define SSG1EOF_OFFSET 0x78 + +#define HFCNTR_CFG 0x944 +#define ITP_DELTA_CLK (0xa << 1) +#define ITP_DELTA_CLK_MASK GENMASK(5, 1) +#define FRMCNT_LEV1_RANG (0x12b << 8) +#define FRMCNT_LEV1_RANG_MASK GENMASK(19, 8) + +#define SS_GEN2_EOF_CFG 0x990 +#define SSG2EOF_OFFSET 0x3c + +#define XSEOF_OFFSET_MASK GENMASK(11, 0) + /* usb remote wakeup registers in syscon */ /* mt8173 etc */ @@ -86,6 +107,46 @@ enum ssusb_uwk_vers { SSUSB_UWK_V1_2, /* specific revision 1.2 */ }; +/* + * MT8195 has 4 controllers, the controller1~3's default SOF/ITP interval + * is calculated from the frame counter clock 24M, but in fact, the clock + * is 48M, add workaround for it. + */ +static void xhci_mtk_set_frame_interval(struct xhci_hcd_mtk *mtk) +{ + struct device *dev = mtk->dev; + struct usb_hcd *hcd = mtk->hcd; + u32 value; + + if (!of_device_is_compatible(dev->of_node, "mediatek,mt8195-xhci")) + return; + + value = readl(hcd->regs + HFCNTR_CFG); + value &= ~(ITP_DELTA_CLK_MASK | FRMCNT_LEV1_RANG_MASK); + value |= (ITP_DELTA_CLK | FRMCNT_LEV1_RANG); + writel(value, hcd->regs + HFCNTR_CFG); + + value = readl(hcd->regs + LS_EOF_CFG); + value &= ~XSEOF_OFFSET_MASK; + value |= LSEOF_OFFSET; + writel(value, hcd->regs + LS_EOF_CFG); + + value = readl(hcd->regs + FS_EOF_CFG); + value &= ~XSEOF_OFFSET_MASK; + value |= FSEOF_OFFSET; + writel(value, hcd->regs + FS_EOF_CFG); + + value = readl(hcd->regs + SS_GEN1_EOF_CFG); + value &= ~XSEOF_OFFSET_MASK; + value |= SSG1EOF_OFFSET; + writel(value, hcd->regs + SS_GEN1_EOF_CFG); + + value = readl(hcd->regs + SS_GEN2_EOF_CFG); + value &= ~XSEOF_OFFSET_MASK; + value |= SSG2EOF_OFFSET; + writel(value, hcd->regs + SS_GEN2_EOF_CFG); +} + static int xhci_mtk_host_enable(struct xhci_hcd_mtk *mtk) { struct mu3c_ippc_regs __iomem *ippc = mtk->ippc_regs; @@ -367,6 +428,9 @@ static int xhci_mtk_setup(struct usb_hcd *hcd) ret = xhci_mtk_ssusb_config(mtk); if (ret) return ret; + + /* workaround only for mt8195 */ + xhci_mtk_set_frame_interval(mtk); } ret = xhci_gen_setup(hcd, xhci_mtk_quirks); @@ -712,6 +776,7 @@ static const struct dev_pm_ops xhci_mtk_pm_ops = { static const struct of_device_id mtk_xhci_of_match[] = { { .compatible = "mediatek,mt8173-xhci"}, + { .compatible = "mediatek,mt8195-xhci"}, { .compatible = "mediatek,mtk-xhci"}, { }, }; -- cgit v1.2.3 From 4ce186665e7c3e9edde648dfb373ca0d213fb312 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Thu, 26 Aug 2021 10:51:43 +0800 Subject: usb: xhci-mtk: Do not use xhci's virt_dev in drop_endpoint xhci-mtk depends on xhci's internal virt_dev when it retrieves its internal data from usb_host_endpoint both in add_endpoint and drop_endpoint callbacks. But when setup packet was retired by transaction errors in xhci_setup_device() path, a virt_dev for the slot is newly created with real_port 0. This leads to xhci-mtks's NULL pointer dereference from drop_endpoint callback as xhci-mtk assumes that virt_dev's real_port is always started from one. The similar problems were addressed by [1] but that can't cover the failure cases from setup_device. This patch drops the usages of xhci's virt_dev in xhci-mtk's drop_endpoint callback by adopting hashtable for searching mtk's schedule entity from a given usb_host_endpoint pointer instead of searching a linked list. So mtk's drop_endpoint callback doesn't have to rely on virt_dev at all. [1] f351f4b63dac ("usb: xhci-mtk: fix oops when unbind driver") Signed-off-by: Ikjoon Jang Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20210826025144.51992-5-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mtk-sch.c | 100 ++++++++++++++++++++-------------------- drivers/usb/host/xhci-mtk.h | 11 ++++- 2 files changed, 60 insertions(+), 51 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c index a9fcf7e30c41..f0ceede85ea5 100644 --- a/drivers/usb/host/xhci-mtk-sch.c +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -80,7 +80,7 @@ decode_ep(struct usb_host_endpoint *ep, enum usb_device_speed speed) interval /= 1000; } - snprintf(buf, DBG_BUF_EN, "%s ep%d%s %s, mpkt:%d, interval:%d/%d%s\n", + snprintf(buf, DBG_BUF_EN, "%s ep%d%s %s, mpkt:%d, interval:%d/%d%s", usb_speed_string(speed), usb_endpoint_num(epd), usb_endpoint_dir_in(epd) ? "in" : "out", usb_ep_type_string(usb_endpoint_type(epd)), @@ -129,6 +129,10 @@ get_bw_info(struct xhci_hcd_mtk *mtk, struct usb_device *udev, int bw_index; virt_dev = xhci->devs[udev->slot_id]; + if (!virt_dev->real_port) { + WARN_ONCE(1, "%s invalid real_port\n", dev_name(&udev->dev)); + return NULL; + } if (udev->speed >= USB_SPEED_SUPER) { if (usb_endpoint_dir_out(&ep->desc)) @@ -236,14 +240,20 @@ static void drop_tt(struct usb_device *udev) } } -static struct mu3h_sch_ep_info *create_sch_ep(struct usb_device *udev, - struct usb_host_endpoint *ep, struct xhci_ep_ctx *ep_ctx) +static struct mu3h_sch_ep_info * +create_sch_ep(struct xhci_hcd_mtk *mtk, 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_bw_info *bw_info; struct mu3h_sch_tt *tt = NULL; u32 len_bw_budget_table; size_t mem_size; + bw_info = get_bw_info(mtk, udev, ep); + if (!bw_info) + return ERR_PTR(-ENODEV); + if (is_fs_or_ls(udev->speed)) len_bw_budget_table = TT_MICROFRAMES_MAX; else if ((udev->speed >= USB_SPEED_SUPER) @@ -266,11 +276,13 @@ static struct mu3h_sch_ep_info *create_sch_ep(struct usb_device *udev, } } + sch_ep->bw_info = bw_info; sch_ep->sch_tt = tt; sch_ep->ep = ep; sch_ep->speed = udev->speed; INIT_LIST_HEAD(&sch_ep->endpoint); INIT_LIST_HEAD(&sch_ep->tt_endpoint); + INIT_HLIST_NODE(&sch_ep->hentry); return sch_ep; } @@ -578,9 +590,9 @@ static u32 get_esit_boundary(struct mu3h_sch_ep_info *sch_ep) return boundary; } -static int check_sch_bw(struct mu3h_sch_bw_info *sch_bw, - struct mu3h_sch_ep_info *sch_ep) +static int check_sch_bw(struct mu3h_sch_ep_info *sch_ep) { + struct mu3h_sch_bw_info *sch_bw = sch_ep->bw_info; const u32 esit_boundary = get_esit_boundary(sch_ep); const u32 bw_boundary = get_bw_boundary(sch_ep->speed); u32 offset; @@ -627,23 +639,26 @@ static int check_sch_bw(struct mu3h_sch_bw_info *sch_bw, return load_ep_bw(sch_bw, sch_ep, true); } -static void destroy_sch_ep(struct usb_device *udev, - struct mu3h_sch_bw_info *sch_bw, struct mu3h_sch_ep_info *sch_ep) +static void destroy_sch_ep(struct xhci_hcd_mtk *mtk, struct usb_device *udev, + struct mu3h_sch_ep_info *sch_ep) { /* only release ep bw check passed by check_sch_bw() */ if (sch_ep->allocated) - load_ep_bw(sch_bw, sch_ep, false); + load_ep_bw(sch_ep->bw_info, sch_ep, false); if (sch_ep->sch_tt) drop_tt(udev); list_del(&sch_ep->endpoint); + hlist_del(&sch_ep->hentry); kfree(sch_ep); } -static bool need_bw_sch(struct usb_host_endpoint *ep, - enum usb_device_speed speed, int has_tt) +static bool need_bw_sch(struct usb_device *udev, + struct usb_host_endpoint *ep) { + bool has_tt = udev->tt && udev->tt->hub->parent; + /* only for periodic endpoints */ if (usb_endpoint_xfer_control(&ep->desc) || usb_endpoint_xfer_bulk(&ep->desc)) @@ -654,7 +669,7 @@ static bool need_bw_sch(struct usb_host_endpoint *ep, * a TT are also ignored, root-hub will schedule them directly, * but need set @bpkts field of endpoint context to 1. */ - if (is_fs_or_ls(speed) && !has_tt) + if (is_fs_or_ls(udev->speed) && !has_tt) return false; /* skip endpoint with zero maxpkt */ @@ -669,7 +684,6 @@ int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk) struct xhci_hcd *xhci = hcd_to_xhci(mtk->hcd); struct mu3h_sch_bw_info *sch_array; int num_usb_bus; - int i; /* ss IN and OUT are separated */ num_usb_bus = xhci->usb3_rhub.num_ports * 2 + xhci->usb2_rhub.num_ports; @@ -678,12 +692,10 @@ int xhci_mtk_sch_init(struct xhci_hcd_mtk *mtk) if (sch_array == NULL) return -ENOMEM; - for (i = 0; i < num_usb_bus; i++) - INIT_LIST_HEAD(&sch_array[i].bw_ep_list); - mtk->sch_array = sch_array; INIT_LIST_HEAD(&mtk->bw_ep_chk_list); + hash_init(mtk->sch_ep_hash); return 0; } @@ -707,9 +719,7 @@ static int add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, ep_index = xhci_get_endpoint_index(&ep->desc); ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); - xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed)); - - if (!need_bw_sch(ep, udev->speed, !!virt_dev->tt_info)) { + if (!need_bw_sch(udev, ep)) { /* * set @bpkts to 1 if it is LS or FS periodic endpoint, and its * device does not connected through an external HS hub @@ -721,13 +731,16 @@ static int add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, return 0; } - sch_ep = create_sch_ep(udev, ep, ep_ctx); + xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed)); + + sch_ep = create_sch_ep(mtk, udev, ep, ep_ctx); if (IS_ERR_OR_NULL(sch_ep)) return -ENOMEM; setup_sch_info(ep_ctx, sch_ep); list_add_tail(&sch_ep->endpoint, &mtk->bw_ep_chk_list); + hash_add(mtk->sch_ep_hash, &sch_ep->hentry, (unsigned long)ep); return 0; } @@ -737,22 +750,18 @@ static void drop_ep_quirk(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 xhci_virt_device *virt_dev; - struct mu3h_sch_bw_info *sch_bw; - struct mu3h_sch_ep_info *sch_ep, *tmp; - - virt_dev = xhci->devs[udev->slot_id]; - - xhci_dbg(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed)); + struct mu3h_sch_ep_info *sch_ep; + struct hlist_node *hn; - if (!need_bw_sch(ep, udev->speed, !!virt_dev->tt_info)) + if (!need_bw_sch(udev, ep)) return; - sch_bw = get_bw_info(mtk, udev, ep); + xhci_err(xhci, "%s %s\n", __func__, decode_ep(ep, udev->speed)); - list_for_each_entry_safe(sch_ep, tmp, &sch_bw->bw_ep_list, endpoint) { + hash_for_each_possible_safe(mtk->sch_ep_hash, sch_ep, + hn, hentry, (unsigned long)ep) { if (sch_ep->ep == ep) { - destroy_sch_ep(udev, sch_bw, sch_ep); + destroy_sch_ep(mtk, udev, sch_ep); break; } } @@ -763,30 +772,22 @@ 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 xhci_virt_device *virt_dev = xhci->devs[udev->slot_id]; - struct mu3h_sch_bw_info *sch_bw; - struct mu3h_sch_ep_info *sch_ep, *tmp; + struct mu3h_sch_ep_info *sch_ep; int ret; xhci_dbg(xhci, "%s() udev %s\n", __func__, dev_name(&udev->dev)); list_for_each_entry(sch_ep, &mtk->bw_ep_chk_list, endpoint) { - sch_bw = get_bw_info(mtk, udev, sch_ep->ep); + struct xhci_ep_ctx *ep_ctx; + struct usb_host_endpoint *ep = sch_ep->ep; + unsigned int ep_index = xhci_get_endpoint_index(&ep->desc); - ret = check_sch_bw(sch_bw, sch_ep); + ret = check_sch_bw(sch_ep); if (ret) { xhci_err(xhci, "Not enough bandwidth! (%s)\n", sch_error_string(-ret)); return -ENOSPC; } - } - - list_for_each_entry_safe(sch_ep, tmp, &mtk->bw_ep_chk_list, endpoint) { - struct xhci_ep_ctx *ep_ctx; - struct usb_host_endpoint *ep = sch_ep->ep; - unsigned int ep_index = xhci_get_endpoint_index(&ep->desc); - - sch_bw = get_bw_info(mtk, udev, ep); - list_move_tail(&sch_ep->endpoint, &sch_bw->bw_ep_list); ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); ep_ctx->reserved[0] = cpu_to_le32(EP_BPKTS(sch_ep->pkts) @@ -800,22 +801,23 @@ int xhci_mtk_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) sch_ep->offset, sch_ep->repeat); } - return xhci_check_bandwidth(hcd, udev); + ret = xhci_check_bandwidth(hcd, udev); + if (!ret) + INIT_LIST_HEAD(&mtk->bw_ep_chk_list); + + return ret; } void xhci_mtk_reset_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_bw_info *sch_bw; struct mu3h_sch_ep_info *sch_ep, *tmp; xhci_dbg(xhci, "%s() udev %s\n", __func__, dev_name(&udev->dev)); - list_for_each_entry_safe(sch_ep, tmp, &mtk->bw_ep_chk_list, endpoint) { - sch_bw = get_bw_info(mtk, udev, sch_ep->ep); - destroy_sch_ep(udev, sch_bw, sch_ep); - } + list_for_each_entry_safe(sch_ep, tmp, &mtk->bw_ep_chk_list, endpoint) + destroy_sch_ep(mtk, udev, sch_ep); xhci_reset_bandwidth(hcd, udev); } diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h index 56dc348349af..9c54a597e66b 100644 --- a/drivers/usb/host/xhci-mtk.h +++ b/drivers/usb/host/xhci-mtk.h @@ -10,11 +10,15 @@ #define _XHCI_MTK_H_ #include +#include #include "xhci.h" #define BULK_CLKS_NUM 5 +/* support at most 64 ep, use 32 size hash table */ +#define SCH_EP_HASH_BITS 5 + /** * To simplify scheduler algorithm, set a upper limit for ESIT, * if a synchromous ep's ESIT is larger than @XHCI_MTK_MAX_ESIT, @@ -36,14 +40,12 @@ struct mu3h_sch_tt { * struct mu3h_sch_bw_info: schedule information for bandwidth domain * * @bus_bw: array to keep track of bandwidth already used at each uframes - * @bw_ep_list: eps in the bandwidth domain * * treat a HS root port as a bandwidth domain, but treat a SS root port as * two bandwidth domains, one for IN eps and another for OUT eps. */ struct mu3h_sch_bw_info { u32 bus_bw[XHCI_MTK_MAX_ESIT]; - struct list_head bw_ep_list; }; /** @@ -54,8 +56,10 @@ struct mu3h_sch_bw_info { * @num_budget_microframes: number of continuous uframes * (@repeat==1) scheduled within the interval * @bw_cost_per_microframe: bandwidth cost per microframe + * @hentry: hash table entry * @endpoint: linked into bandwidth domain which it belongs to * @tt_endpoint: linked into mu3h_sch_tt's list which it belongs to + * @bw_info: bandwidth domain which this endpoint belongs * @sch_tt: mu3h_sch_tt linked into * @ep_type: endpoint type * @maxpkt: max packet size of endpoint @@ -84,7 +88,9 @@ struct mu3h_sch_ep_info { u32 num_budget_microframes; u32 bw_cost_per_microframe; struct list_head endpoint; + struct hlist_node hentry; struct list_head tt_endpoint; + struct mu3h_sch_bw_info *bw_info; struct mu3h_sch_tt *sch_tt; u32 ep_type; u32 maxpkt; @@ -137,6 +143,7 @@ struct xhci_hcd_mtk { struct usb_hcd *hcd; struct mu3h_sch_bw_info *sch_array; struct list_head bw_ep_chk_list; + DECLARE_HASHTABLE(sch_ep_hash, SCH_EP_HASH_BITS); struct mu3c_ippc_regs __iomem *ippc_regs; int num_u2_ports; int num_u3_ports; -- cgit v1.2.3 From 50fdcb56c41904c3535687a0e1e1dbd9423a8f9a Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Thu, 26 Aug 2021 16:36:36 +0800 Subject: usb: mtu3: return successful suspend status Forgot 'return 0;' when suspend successfully, make the mistake when I split patches. Fixes: 6b587394c65c ("usb: mtu3: support suspend/resume for dual-role mode") Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20210826083637.33237-1-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_plat.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index 81266f34986f..5b3f7f73cb40 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -502,6 +502,7 @@ static int mtu3_suspend_common(struct device *dev, pm_message_t msg) ssusb_phy_power_off(ssusb); clk_bulk_disable_unprepare(BULK_CLKS_CNT, ssusb->clks); ssusb_wakeup_set(ssusb, true); + return 0; sleep_err: resume_ip_and_ports(ssusb, msg); -- cgit v1.2.3 From d98a30ccdc839947c9233369744341d1fa54439c Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Thu, 26 Aug 2021 16:36:37 +0800 Subject: usb: mtu3: fix random remote wakeup Some platforms, e.g. 8183/8192, use low level latch way to keep wakeup signal, it may latch a wrong signal if debounce more time, and enable wakeup earlier. ____________________ ip_sleep ____/ \__________ ___________________ wakeup_signal ____________/ \______ _______________________________ wakeup_en _______/ ^ ^ |(1) |(2) latch wakeup_signal mistakenly at (1), should latch it at (2); Workaround: delay about 100us to enable wakeup, meanwhile decrease debounce time. Fixes: b1a344589eea ("usb: mtu3: support ip-sleep wakeup for MT8183") Cc: stable@vger.kernel.org Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20210826083637.33237-2-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_host.c | 2 +- drivers/usb/mtu3/mtu3_plat.c | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/mtu3/mtu3_host.c b/drivers/usb/mtu3/mtu3_host.c index 7d528f3c2482..f3903367a6a0 100644 --- a/drivers/usb/mtu3/mtu3_host.c +++ b/drivers/usb/mtu3/mtu3_host.c @@ -62,7 +62,7 @@ static void ssusb_wakeup_ip_sleep_set(struct ssusb_mtk *ssusb, bool enable) case SSUSB_UWK_V1_1: reg = ssusb->uwk_reg_base + PERI_WK_CTRL0; msk = WC0_IS_EN | WC0_IS_C(0xf) | WC0_IS_P; - val = enable ? (WC0_IS_EN | WC0_IS_C(0x8)) : 0; + val = enable ? (WC0_IS_EN | WC0_IS_C(0x1)) : 0; break; case SSUSB_UWK_V1_2: reg = ssusb->uwk_reg_base + PERI_WK_CTRL0; diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index 5b3f7f73cb40..f13531022f4a 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -63,6 +63,9 @@ static int wait_for_ip_sleep(struct ssusb_mtk *ssusb) if (ret) { dev_err(ssusb->dev, "ip sleep failed!!!\n"); ret = -EBUSY; + } else { + /* workaround: avoid wrong wakeup signal latch for some soc */ + usleep_range(100, 200); } return ret; -- cgit v1.2.3 From b7d509a92bb0216b01f428166be3770d04bbdd87 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun Date: Fri, 27 Aug 2021 11:31:05 +0800 Subject: usb: xhci-mtk: allow bandwidth table rollover xhci-mtk has 64 slots for periodic bandwidth calculations and each slot represents byte budgets on a microframe. When an endpoint's allocation sits on the boundary of the table, byte budgets' slot can be rolled over but the current implementation doesn't. This patch allows the microframe index rollover and prevent out-of-bounds array access. Signed-off-by: Ikjoon Jang Signed-off-by: Chunfeng Yun Link: https://lore.kernel.org/r/20210827033105.26595-1-chunfeng.yun@mediatek.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mtk-sch.c | 54 ++++++++++++----------------------------- drivers/usb/host/xhci-mtk.h | 3 ++- 2 files changed, 18 insertions(+), 39 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c index f0ceede85ea5..134f4789bd89 100644 --- a/drivers/usb/host/xhci-mtk-sch.c +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -416,15 +416,14 @@ static u32 get_max_bw(struct mu3h_sch_bw_info *sch_bw, { u32 max_bw = 0; u32 bw; - int i; - int j; + int i, j, k; for (i = 0; i < sch_ep->num_esit; i++) { u32 base = offset + i * sch_ep->esit; for (j = 0; j < sch_ep->num_budget_microframes; j++) { - bw = sch_bw->bus_bw[base + j] + - sch_ep->bw_budget_table[j]; + k = XHCI_MTK_BW_INDEX(base + j); + bw = sch_bw->bus_bw[k] + sch_ep->bw_budget_table[j]; if (bw > max_bw) max_bw = bw; } @@ -436,18 +435,16 @@ static void update_bus_bw(struct mu3h_sch_bw_info *sch_bw, struct mu3h_sch_ep_info *sch_ep, bool used) { u32 base; - int i; - int j; + int i, j, k; for (i = 0; i < sch_ep->num_esit; i++) { base = sch_ep->offset + i * sch_ep->esit; for (j = 0; j < sch_ep->num_budget_microframes; j++) { + k = XHCI_MTK_BW_INDEX(base + j); if (used) - sch_bw->bus_bw[base + j] += - sch_ep->bw_budget_table[j]; + sch_bw->bus_bw[k] += sch_ep->bw_budget_table[j]; else - sch_bw->bus_bw[base + j] -= - sch_ep->bw_budget_table[j]; + sch_bw->bus_bw[k] -= sch_ep->bw_budget_table[j]; } } } @@ -457,7 +454,7 @@ static int check_fs_bus_bw(struct mu3h_sch_ep_info *sch_ep, int offset) struct mu3h_sch_tt *tt = sch_ep->sch_tt; u32 tmp; int base; - int i, j; + int i, j, k; for (i = 0; i < sch_ep->num_esit; i++) { base = offset + i * sch_ep->esit; @@ -467,7 +464,8 @@ static int check_fs_bus_bw(struct mu3h_sch_ep_info *sch_ep, int offset) * the hub will always delay one uframe to send data */ for (j = 0; j < sch_ep->num_budget_microframes; j++) { - tmp = tt->fs_bus_bw[base + j] + sch_ep->bw_budget_table[j]; + k = XHCI_MTK_BW_INDEX(base + j); + tmp = tt->fs_bus_bw[k] + sch_ep->bw_budget_table[j]; if (tmp > FS_PAYLOAD_MAX) return -ESCH_BW_OVERFLOW; } @@ -542,16 +540,18 @@ static void update_sch_tt(struct mu3h_sch_ep_info *sch_ep, bool used) { struct mu3h_sch_tt *tt = sch_ep->sch_tt; u32 base; - int i, j; + int i, j, k; for (i = 0; i < sch_ep->num_esit; i++) { base = sch_ep->offset + i * sch_ep->esit; - for (j = 0; j < sch_ep->num_budget_microframes; j++) + for (j = 0; j < sch_ep->num_budget_microframes; j++) { + k = XHCI_MTK_BW_INDEX(base + j); if (used) - tt->fs_bus_bw[base + j] += sch_ep->bw_budget_table[j]; + tt->fs_bus_bw[k] += sch_ep->bw_budget_table[j]; else - tt->fs_bus_bw[base + j] -= sch_ep->bw_budget_table[j]; + tt->fs_bus_bw[k] -= sch_ep->bw_budget_table[j]; + } } if (used) @@ -573,27 +573,9 @@ static int load_ep_bw(struct mu3h_sch_bw_info *sch_bw, return 0; } -static u32 get_esit_boundary(struct mu3h_sch_ep_info *sch_ep) -{ - u32 boundary = sch_ep->esit; - - if (sch_ep->sch_tt) { /* LS/FS with TT */ - /* - * tune for CS, normally esit >= 8 for FS/LS, - * not add one for other types to avoid access array - * out of boundary - */ - if (sch_ep->ep_type == ISOC_OUT_EP && boundary > 1) - boundary--; - } - - return boundary; -} - static int check_sch_bw(struct mu3h_sch_ep_info *sch_ep) { struct mu3h_sch_bw_info *sch_bw = sch_ep->bw_info; - const u32 esit_boundary = get_esit_boundary(sch_ep); const u32 bw_boundary = get_bw_boundary(sch_ep->speed); u32 offset; u32 worst_bw; @@ -606,10 +588,6 @@ static int check_sch_bw(struct mu3h_sch_ep_info *sch_ep) * and find a microframe where its worst bandwidth is minimum. */ for (offset = 0; offset < sch_ep->esit; offset++) { - - if ((offset + sch_ep->num_budget_microframes) > esit_boundary) - break; - ret = check_sch_tt(sch_ep, offset); if (ret) continue; diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h index 9c54a597e66b..4b1ea89f959a 100644 --- a/drivers/usb/host/xhci-mtk.h +++ b/drivers/usb/host/xhci-mtk.h @@ -25,7 +25,8 @@ * round down to the limit value, that means allocating more * bandwidth to it. */ -#define XHCI_MTK_MAX_ESIT 64 +#define XHCI_MTK_MAX_ESIT (1 << 6) +#define XHCI_MTK_BW_INDEX(x) ((x) & (XHCI_MTK_MAX_ESIT - 1)) /** * @fs_bus_bw: array to keep track of bandwidth already used for FS -- cgit v1.2.3 From 57f3ffdc11143f56f1314972fe86fe17a0dcde85 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Fri, 27 Aug 2021 15:32:27 +0900 Subject: usb: host: xhci-rcar: Don't reload firmware after the completion According to the datasheet, "Upon the completion of FW Download, there is no need to write or reload FW.". Otherwise, it's possible to cause unexpected behaviors. So, adds such a condition. Fixes: 4ac8918f3a73 ("usb: host: xhci-plat: add support for the R-Car H2 and M2 xHCI controllers") Cc: stable@vger.kernel.org # v3.17+ Signed-off-by: Yoshihiro Shimoda Link: https://lore.kernel.org/r/20210827063227.81990-1-yoshihiro.shimoda.uh@renesas.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-rcar.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers') diff --git a/drivers/usb/host/xhci-rcar.c b/drivers/usb/host/xhci-rcar.c index 1bc4fe7b8c75..9888ba7d85b6 100644 --- a/drivers/usb/host/xhci-rcar.c +++ b/drivers/usb/host/xhci-rcar.c @@ -134,6 +134,13 @@ static int xhci_rcar_download_firmware(struct usb_hcd *hcd) const struct soc_device_attribute *attr; const char *firmware_name; + /* + * According to the datasheet, "Upon the completion of FW Download, + * there is no need to write or reload FW". + */ + if (readl(regs + RCAR_USB3_DL_CTRL) & RCAR_USB3_DL_CTRL_FW_SUCCESS) + return 0; + attr = soc_device_match(rcar_quirks_match); if (attr) quirks = (uintptr_t)attr->data; -- cgit v1.2.3 From cc7f8825cdbb05fa10782a34732f5d8082daa5c3 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 26 Aug 2021 13:26:58 +0100 Subject: usb: renesas_usbhs: Fix spelling mistake "faile" -> "failed" There is a spelling mistake in a dev_err error message. Fix it. Reviewed-by: Yoshihiro Shimoda Signed-off-by: Colin Ian King Link: https://lore.kernel.org/r/20210826122658.13914-1-colin.king@canonical.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/renesas_usbhs/fifo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/renesas_usbhs/fifo.c b/drivers/usb/renesas_usbhs/fifo.c index a3c2b01ccf7b..10607e273879 100644 --- a/drivers/usb/renesas_usbhs/fifo.c +++ b/drivers/usb/renesas_usbhs/fifo.c @@ -366,7 +366,7 @@ static int usbhs_dcp_dir_switch_to_write(struct usbhs_pkt *pkt, int *is_done) ret = usbhsf_fifo_select(pipe, fifo, 1); if (ret < 0) { - dev_err(dev, "%s() faile\n", __func__); + dev_err(dev, "%s() failed\n", __func__); return ret; } -- cgit v1.2.3 From a76cb3d999b12219620d41a186ec2af2b247ac71 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 26 Aug 2021 15:38:49 +0100 Subject: usb: dwc2: Fix spelling mistake "was't" -> "wasn't" There is a spelling mistake in a dev_dbg message. Fix it. Signed-off-by: Colin Ian King Link: https://lore.kernel.org/r/20210826143849.55115-1-colin.king@canonical.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 272ae5722c86..cf0bcd0dc320 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -295,7 +295,7 @@ void dwc2_hib_restore_common(struct dwc2_hsotg *hsotg, int rem_wakeup, if (dwc2_hsotg_wait_bit_set(hsotg, GINTSTS, GINTSTS_RESTOREDONE, 20000)) { dev_dbg(hsotg->dev, - "%s: Restore Done wan't generated here\n", + "%s: Restore Done wasn't generated here\n", __func__); } else { dev_dbg(hsotg->dev, "restore done generated here\n"); -- cgit v1.2.3 From f73800a905a8a9c35b989e8de9ce5cdf328c2b63 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 26 Aug 2021 13:39:59 +0100 Subject: usb: typec: tcpm: Fix spelling mistake "atleast" -> "at least" There are spelling mistakes in a comment and a literal string. Fix them. Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Colin Ian King Link: https://lore.kernel.org/r/20210826123959.14838-1-colin.king@canonical.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 049f4c61ee82..b981fc39fa3c 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -278,7 +278,7 @@ struct pd_mode_data { * @req_max_curr: Requested max current of the port partner * @req_out_volt: Requested output voltage to the port partner * @req_op_curr: Requested operating current to the port partner - * @supported: Parter has atleast one APDO hence supports PPS + * @supported: Parter has at least one APDO hence supports PPS * @active: PPS mode is active */ struct pd_pps_data { @@ -2050,7 +2050,7 @@ enum pdo_err { static const char * const pdo_err_msg[] = { [PDO_ERR_NO_VSAFE5V] = - " err: source/sink caps should atleast have vSafe5V", + " err: source/sink caps should at least have vSafe5V", [PDO_ERR_VSAFE5V_NOT_FIRST] = " err: vSafe5V Fixed Supply Object Shall always be the first object", [PDO_ERR_PDO_TYPE_NOT_IN_ORDER] = -- cgit v1.2.3 From f757f9291f920e1da4c6cfd4064c6bf59639983e Mon Sep 17 00:00:00 2001 From: Rui Miguel Silva Date: Fri, 27 Aug 2021 14:11:50 +0100 Subject: usb: isp1760: fix memory pool initialization The loops to setup the memory pool were skipping some blocks, that was not visible on the ISP1763 because it has fewer blocks than the ISP1761. But won testing on that IP from the family that would be an issue. Reported-by: Dietmar Eggemann Tested-by: Dietmar Eggemann Signed-off-by: Rui Miguel Silva Link: https://lore.kernel.org/r/20210827131154.4151862-2-rui.silva@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/isp1760/isp1760-hcd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c index bf8ab3fe2e5a..b3a55c5d2155 100644 --- a/drivers/usb/isp1760/isp1760-hcd.c +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -588,8 +588,8 @@ static void init_memory(struct isp1760_hcd *priv) payload_addr = PAYLOAD_OFFSET; - for (i = 0, curr = 0; i < ARRAY_SIZE(mem->blocks); i++) { - for (j = 0; j < mem->blocks[i]; j++, curr++) { + for (i = 0, curr = 0; i < ARRAY_SIZE(mem->blocks); i++, curr += j) { + for (j = 0; j < mem->blocks[i]; j++) { priv->memory_pool[curr + j].start = payload_addr; priv->memory_pool[curr + j].size = mem->blocks_size[i]; priv->memory_pool[curr + j].free = 1; -- cgit v1.2.3 From cbfa3effdf5c2d411c9ce9820f3d33d77bc4697d Mon Sep 17 00:00:00 2001 From: Rui Miguel Silva Date: Fri, 27 Aug 2021 14:11:51 +0100 Subject: usb: isp1760: fix qtd fill length When trying to send bulks bigger than the biggest block size we need to split them over several qtd. Fix this limiting the maximum qtd size to largest block size. Reported-by: Dietmar Eggemann Tested-by: Dietmar Eggemann Signed-off-by: Rui Miguel Silva Link: https://lore.kernel.org/r/20210827131154.4151862-3-rui.silva@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/isp1760/isp1760-hcd.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c index b3a55c5d2155..fba21122bb00 100644 --- a/drivers/usb/isp1760/isp1760-hcd.c +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -1829,9 +1829,11 @@ static void packetize_urb(struct usb_hcd *hcd, goto cleanup; if (len > mem->blocks_size[ISP176x_BLOCK_NUM - 1]) - len = mem->blocks_size[ISP176x_BLOCK_NUM - 1]; + this_qtd_len = mem->blocks_size[ISP176x_BLOCK_NUM - 1]; + else + this_qtd_len = len; - this_qtd_len = qtd_fill(qtd, buf, len); + this_qtd_len = qtd_fill(qtd, buf, this_qtd_len); list_add_tail(&qtd->qtd_list, head); len -= this_qtd_len; -- cgit v1.2.3 From 36815a4a0763bb405ebd776c45553005c1ef7a15 Mon Sep 17 00:00:00 2001 From: Rui Miguel Silva Date: Fri, 27 Aug 2021 14:11:52 +0100 Subject: usb: isp1760: write to status and address register We were already writing directly the port status register to trigger changes in isp1763. The same is needed in other IP from the family, including also to setup the read address before reading from device. Reported-by: Dietmar Eggemann Tested-by: Dietmar Eggemann Signed-off-by: Rui Miguel Silva Link: https://lore.kernel.org/r/20210827131154.4151862-4-rui.silva@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/isp1760/isp1760-hcd.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c index fba21122bb00..79d571f1429b 100644 --- a/drivers/usb/isp1760/isp1760-hcd.c +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -182,7 +182,7 @@ struct urb_listitem { struct urb *urb; }; -static const u32 isp1763_hc_portsc1_fields[] = { +static const u32 isp176x_hc_portsc1_fields[] = { [PORT_OWNER] = BIT(13), [PORT_POWER] = BIT(12), [PORT_LSTATUS] = BIT(10), @@ -205,27 +205,28 @@ static u32 isp1760_hcd_read(struct usb_hcd *hcd, u32 field) } /* - * We need, in isp1763, to write directly the values to the portsc1 + * We need, in isp176x, to write directly the values to the portsc1 * register so it will make the other values to trigger. */ static void isp1760_hcd_portsc1_set_clear(struct isp1760_hcd *priv, u32 field, u32 val) { - u32 bit = isp1763_hc_portsc1_fields[field]; - u32 port_status = readl(priv->base + ISP1763_HC_PORTSC1); + u32 bit = isp176x_hc_portsc1_fields[field]; + u16 portsc1_reg = priv->is_isp1763 ? ISP1763_HC_PORTSC1 : + ISP176x_HC_PORTSC1; + u32 port_status = readl(priv->base + portsc1_reg); if (val) - writel(port_status | bit, priv->base + ISP1763_HC_PORTSC1); + writel(port_status | bit, priv->base + portsc1_reg); else - writel(port_status & ~bit, priv->base + ISP1763_HC_PORTSC1); + writel(port_status & ~bit, priv->base + portsc1_reg); } static void isp1760_hcd_write(struct usb_hcd *hcd, u32 field, u32 val) { struct isp1760_hcd *priv = hcd_to_priv(hcd); - if (unlikely(priv->is_isp1763 && - (field >= PORT_OWNER && field <= PORT_CONNECT))) + if (unlikely((field >= PORT_OWNER && field <= PORT_CONNECT))) return isp1760_hcd_portsc1_set_clear(priv, field, val); isp1760_field_write(priv->fields, field, val); @@ -367,8 +368,7 @@ static void isp1760_mem_read(struct usb_hcd *hcd, u32 src_offset, void *dst, { struct isp1760_hcd *priv = hcd_to_priv(hcd); - isp1760_hcd_write(hcd, MEM_BANK_SEL, ISP_BANK_0); - isp1760_hcd_write(hcd, MEM_START_ADDR, src_offset); + isp1760_reg_write(priv->regs, ISP176x_HC_MEMORY, src_offset); ndelay(100); bank_reads8(priv->base, src_offset, ISP_BANK_0, dst, bytes); @@ -496,8 +496,7 @@ static void isp1760_ptd_read(struct usb_hcd *hcd, u32 ptd_offset, u32 slot, u16 src_offset = ptd_offset + slot * sizeof(*ptd); struct isp1760_hcd *priv = hcd_to_priv(hcd); - isp1760_hcd_write(hcd, MEM_BANK_SEL, ISP_BANK_0); - isp1760_hcd_write(hcd, MEM_START_ADDR, src_offset); + isp1760_reg_write(priv->regs, ISP176x_HC_MEMORY, src_offset); ndelay(90); bank_reads8(priv->base, src_offset, ISP_BANK_0, (void *)ptd, -- cgit v1.2.3 From 955d0fb590f18ec5c3a4085c7d0e39b6abde0dd6 Mon Sep 17 00:00:00 2001 From: Rui Miguel Silva Date: Fri, 27 Aug 2021 14:11:53 +0100 Subject: usb: isp1760: use the right irq status bit Instead of using the fields enum values to check interrupts trigged, use the correct bit values. Reported-by: Dietmar Eggemann Tested-by: Dietmar Eggemann Signed-off-by: Rui Miguel Silva Link: https://lore.kernel.org/r/20210827131154.4151862-5-rui.silva@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/isp1760/isp1760-udc.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/isp1760/isp1760-udc.c b/drivers/usb/isp1760/isp1760-udc.c index a78da59d6417..5cafd23345ca 100644 --- a/drivers/usb/isp1760/isp1760-udc.c +++ b/drivers/usb/isp1760/isp1760-udc.c @@ -1363,7 +1363,7 @@ static irqreturn_t isp1760_udc_irq(int irq, void *dev) status = isp1760_udc_irq_get_status(udc); - if (status & DC_IEVBUS) { + if (status & ISP176x_DC_IEVBUS) { dev_dbg(udc->isp->dev, "%s(VBUS)\n", __func__); /* The VBUS interrupt is only triggered when VBUS appears. */ spin_lock(&udc->lock); @@ -1371,7 +1371,7 @@ static irqreturn_t isp1760_udc_irq(int irq, void *dev) spin_unlock(&udc->lock); } - if (status & DC_IEBRST) { + if (status & ISP176x_DC_IEBRST) { dev_dbg(udc->isp->dev, "%s(BRST)\n", __func__); isp1760_udc_reset(udc); @@ -1391,18 +1391,18 @@ static irqreturn_t isp1760_udc_irq(int irq, void *dev) } } - if (status & DC_IEP0SETUP) { + if (status & ISP176x_DC_IEP0SETUP) { dev_dbg(udc->isp->dev, "%s(EP0SETUP)\n", __func__); isp1760_ep0_setup(udc); } - if (status & DC_IERESM) { + if (status & ISP176x_DC_IERESM) { dev_dbg(udc->isp->dev, "%s(RESM)\n", __func__); isp1760_udc_resume(udc); } - if (status & DC_IESUSP) { + if (status & ISP176x_DC_IESUSP) { dev_dbg(udc->isp->dev, "%s(SUSP)\n", __func__); spin_lock(&udc->lock); @@ -1413,7 +1413,7 @@ static irqreturn_t isp1760_udc_irq(int irq, void *dev) spin_unlock(&udc->lock); } - if (status & DC_IEHS_STA) { + if (status & ISP176x_DC_IEHS_STA) { dev_dbg(udc->isp->dev, "%s(HS_STA)\n", __func__); udc->gadget.speed = USB_SPEED_HIGH; } -- cgit v1.2.3 From 9c1587d99f9305aa4f10b47fcf1981012aa5381f Mon Sep 17 00:00:00 2001 From: Rui Miguel Silva Date: Fri, 27 Aug 2021 14:11:54 +0100 Subject: usb: isp1760: otg control register access The set/clear of the otg control values is done writing to two different 16bit registers, however we setup the regmap width for isp1760/61 to 32bit value bits. So, just access the clear register address (0x376)as the high part of the otg control register set (0x374), and write the values in one go to make sure they get clear/set. Reported-by: Dietmar Eggemann Tested-by: Dietmar Eggemann Signed-off-by: Rui Miguel Silva Link: https://lore.kernel.org/r/20210827131154.4151862-6-rui.silva@linaro.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/isp1760/isp1760-core.c | 50 ++++++++++++++++++++------------------ drivers/usb/isp1760/isp1760-regs.h | 16 ++++++++++++ 2 files changed, 42 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/isp1760/isp1760-core.c b/drivers/usb/isp1760/isp1760-core.c index cb70f9d63cdd..d1d9a7d5da17 100644 --- a/drivers/usb/isp1760/isp1760-core.c +++ b/drivers/usb/isp1760/isp1760-core.c @@ -30,6 +30,7 @@ static int isp1760_init_core(struct isp1760_device *isp) { struct isp1760_hcd *hcd = &isp->hcd; struct isp1760_udc *udc = &isp->udc; + u32 otg_ctrl; /* Low-level chip reset */ if (isp->rst_gpio) { @@ -83,16 +84,17 @@ static int isp1760_init_core(struct isp1760_device *isp) * * TODO: Really support OTG. For now we configure port 1 in device mode */ - if (((isp->devflags & ISP1760_FLAG_ISP1761) || - (isp->devflags & ISP1760_FLAG_ISP1763)) && - (isp->devflags & ISP1760_FLAG_PERIPHERAL_EN)) { - isp1760_field_set(hcd->fields, HW_DM_PULLDOWN); - isp1760_field_set(hcd->fields, HW_DP_PULLDOWN); - isp1760_field_set(hcd->fields, HW_OTG_DISABLE); - } else { - isp1760_field_set(hcd->fields, HW_SW_SEL_HC_DC); - isp1760_field_set(hcd->fields, HW_VBUS_DRV); - isp1760_field_set(hcd->fields, HW_SEL_CP_EXT); + if (isp->devflags & ISP1760_FLAG_ISP1761) { + if (isp->devflags & ISP1760_FLAG_PERIPHERAL_EN) { + otg_ctrl = (ISP176x_HW_DM_PULLDOWN_CLEAR | + ISP176x_HW_DP_PULLDOWN_CLEAR | + ISP176x_HW_OTG_DISABLE); + } else { + otg_ctrl = (ISP176x_HW_SW_SEL_HC_DC_CLEAR | + ISP176x_HW_VBUS_DRV | + ISP176x_HW_SEL_CP_EXT); + } + isp1760_reg_write(hcd->regs, ISP176x_HC_OTG_CTRL, otg_ctrl); } dev_info(isp->dev, "%s bus width: %u, oc: %s\n", @@ -235,20 +237,20 @@ static const struct reg_field isp1760_hc_reg_fields[] = { [HC_ISO_IRQ_MASK_AND] = REG_FIELD(ISP176x_HC_ISO_IRQ_MASK_AND, 0, 31), [HC_INT_IRQ_MASK_AND] = REG_FIELD(ISP176x_HC_INT_IRQ_MASK_AND, 0, 31), [HC_ATL_IRQ_MASK_AND] = REG_FIELD(ISP176x_HC_ATL_IRQ_MASK_AND, 0, 31), - [HW_OTG_DISABLE] = REG_FIELD(ISP176x_HC_OTG_CTRL_SET, 10, 10), - [HW_SW_SEL_HC_DC] = REG_FIELD(ISP176x_HC_OTG_CTRL_SET, 7, 7), - [HW_VBUS_DRV] = REG_FIELD(ISP176x_HC_OTG_CTRL_SET, 4, 4), - [HW_SEL_CP_EXT] = REG_FIELD(ISP176x_HC_OTG_CTRL_SET, 3, 3), - [HW_DM_PULLDOWN] = REG_FIELD(ISP176x_HC_OTG_CTRL_SET, 2, 2), - [HW_DP_PULLDOWN] = REG_FIELD(ISP176x_HC_OTG_CTRL_SET, 1, 1), - [HW_DP_PULLUP] = REG_FIELD(ISP176x_HC_OTG_CTRL_SET, 0, 0), - [HW_OTG_DISABLE_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL_CLEAR, 10, 10), - [HW_SW_SEL_HC_DC_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL_CLEAR, 7, 7), - [HW_VBUS_DRV_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL_CLEAR, 4, 4), - [HW_SEL_CP_EXT_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL_CLEAR, 3, 3), - [HW_DM_PULLDOWN_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL_CLEAR, 2, 2), - [HW_DP_PULLDOWN_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL_CLEAR, 1, 1), - [HW_DP_PULLUP_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL_CLEAR, 0, 0), + [HW_OTG_DISABLE_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL, 26, 26), + [HW_SW_SEL_HC_DC_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL, 23, 23), + [HW_VBUS_DRV_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL, 20, 20), + [HW_SEL_CP_EXT_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL, 19, 19), + [HW_DM_PULLDOWN_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL, 18, 18), + [HW_DP_PULLDOWN_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL, 17, 17), + [HW_DP_PULLUP_CLEAR] = REG_FIELD(ISP176x_HC_OTG_CTRL, 16, 16), + [HW_OTG_DISABLE] = REG_FIELD(ISP176x_HC_OTG_CTRL, 10, 10), + [HW_SW_SEL_HC_DC] = REG_FIELD(ISP176x_HC_OTG_CTRL, 7, 7), + [HW_VBUS_DRV] = REG_FIELD(ISP176x_HC_OTG_CTRL, 4, 4), + [HW_SEL_CP_EXT] = REG_FIELD(ISP176x_HC_OTG_CTRL, 3, 3), + [HW_DM_PULLDOWN] = REG_FIELD(ISP176x_HC_OTG_CTRL, 2, 2), + [HW_DP_PULLDOWN] = REG_FIELD(ISP176x_HC_OTG_CTRL, 1, 1), + [HW_DP_PULLUP] = REG_FIELD(ISP176x_HC_OTG_CTRL, 0, 0), }; static const struct reg_field isp1763_hc_reg_fields[] = { diff --git a/drivers/usb/isp1760/isp1760-regs.h b/drivers/usb/isp1760/isp1760-regs.h index 94ea60c20b2a..3a6751197e97 100644 --- a/drivers/usb/isp1760/isp1760-regs.h +++ b/drivers/usb/isp1760/isp1760-regs.h @@ -61,6 +61,7 @@ #define ISP176x_HC_INT_IRQ_MASK_AND 0x328 #define ISP176x_HC_ATL_IRQ_MASK_AND 0x32c +#define ISP176x_HC_OTG_CTRL 0x374 #define ISP176x_HC_OTG_CTRL_SET 0x374 #define ISP176x_HC_OTG_CTRL_CLEAR 0x376 @@ -179,6 +180,21 @@ enum isp176x_host_controller_fields { #define ISP176x_DC_IESUSP BIT(3) #define ISP176x_DC_IEBRST BIT(0) +#define ISP176x_HW_OTG_DISABLE_CLEAR BIT(26) +#define ISP176x_HW_SW_SEL_HC_DC_CLEAR BIT(23) +#define ISP176x_HW_VBUS_DRV_CLEAR BIT(20) +#define ISP176x_HW_SEL_CP_EXT_CLEAR BIT(19) +#define ISP176x_HW_DM_PULLDOWN_CLEAR BIT(18) +#define ISP176x_HW_DP_PULLDOWN_CLEAR BIT(17) +#define ISP176x_HW_DP_PULLUP_CLEAR BIT(16) +#define ISP176x_HW_OTG_DISABLE BIT(10) +#define ISP176x_HW_SW_SEL_HC_DC BIT(7) +#define ISP176x_HW_VBUS_DRV BIT(4) +#define ISP176x_HW_SEL_CP_EXT BIT(3) +#define ISP176x_HW_DM_PULLDOWN BIT(2) +#define ISP176x_HW_DP_PULLDOWN BIT(1) +#define ISP176x_HW_DP_PULLUP BIT(0) + #define ISP176x_DC_ENDPTYP_ISOC 0x01 #define ISP176x_DC_ENDPTYP_BULK 0x02 #define ISP176x_DC_ENDPTYP_INTERRUPT 0x03 -- cgit v1.2.3