diff options
59 files changed, 2744 insertions, 342 deletions
diff --git a/Documentation/devicetree/bindings/usb/dwc2.yaml b/Documentation/devicetree/bindings/usb/dwc2.yaml index 56a818478cd7..f00867ebc147 100644 --- a/Documentation/devicetree/bindings/usb/dwc2.yaml +++ b/Documentation/devicetree/bindings/usb/dwc2.yaml @@ -114,6 +114,8 @@ properties: usb-role-switch: true + role-switch-default-mode: true + g-rx-fifo-size: $ref: /schemas/types.yaml#/definitions/uint32 description: size of rx fifo size in gadget mode. @@ -136,6 +138,17 @@ properties: description: If present indicates that we need to reset the PHY when we detect a wakeup. This is due to a hardware errata. + port: + description: + Any connector to the data bus of this controller should be modelled + using the OF graph bindings specified, if the "usb-role-switch" + property is used. + $ref: /schemas/graph.yaml#/properties/port + +dependencies: + port: [ usb-role-switch ] + role-switch-default-mode: [ usb-role-switch ] + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/usb/dwc3-xilinx.txt b/Documentation/devicetree/bindings/usb/dwc3-xilinx.txt deleted file mode 100644 index 04813a46e5d0..000000000000 --- a/Documentation/devicetree/bindings/usb/dwc3-xilinx.txt +++ /dev/null @@ -1,56 +0,0 @@ -Xilinx SuperSpeed DWC3 USB SoC controller - -Required properties: -- compatible: May contain "xlnx,zynqmp-dwc3" or "xlnx,versal-dwc3" -- reg: Base address and length of the register control block -- clocks: A list of phandles for the clocks listed in clock-names -- clock-names: Should contain the following: - "bus_clk" Master/Core clock, have to be >= 125 MHz for SS - operation and >= 60MHz for HS operation - - "ref_clk" Clock source to core during PHY power down -- resets: A list of phandles for resets listed in reset-names -- reset-names: - "usb_crst" USB core reset - "usb_hibrst" USB hibernation reset - "usb_apbrst" USB APB reset - -Required child node: -A child node must exist to represent the core DWC3 IP block. The name of -the node is not important. The content of the node is defined in dwc3.txt. - -Optional properties for snps,dwc3: -- dma-coherent: Enable this flag if CCI is enabled in design. Adding this - flag configures Global SoC bus Configuration Register and - Xilinx USB 3.0 IP - USB coherency register to enable CCI. -- interrupt-names: Should contain the following: - "dwc_usb3" USB gadget mode interrupts - "otg" USB OTG mode interrupts - "hiber" USB hibernation interrupts - -Example device node: - - usb@0 { - #address-cells = <0x2>; - #size-cells = <0x1>; - compatible = "xlnx,zynqmp-dwc3"; - reg = <0x0 0xff9d0000 0x0 0x100>; - clock-names = "bus_clk", "ref_clk"; - clocks = <&clk125>, <&clk125>; - resets = <&zynqmp_reset ZYNQMP_RESET_USB1_CORERESET>, - <&zynqmp_reset ZYNQMP_RESET_USB1_HIBERRESET>, - <&zynqmp_reset ZYNQMP_RESET_USB1_APB>; - reset-names = "usb_crst", "usb_hibrst", "usb_apbrst"; - ranges; - - dwc3@fe200000 { - compatible = "snps,dwc3"; - reg = <0x0 0xfe200000 0x40000>; - interrupt-names = "dwc_usb3", "otg", "hiber"; - interrupts = <0 65 4>, <0 69 4>, <0 75 4>; - phys = <&psgtr 2 PHY_TYPE_USB3 0 2>; - phy-names = "usb3-phy"; - dr_mode = "host"; - dma-coherent; - }; - }; diff --git a/Documentation/devicetree/bindings/usb/dwc3-xilinx.yaml b/Documentation/devicetree/bindings/usb/dwc3-xilinx.yaml new file mode 100644 index 000000000000..f77c16e203d5 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/dwc3-xilinx.yaml @@ -0,0 +1,131 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/dwc3-xilinx.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Xilinx SuperSpeed DWC3 USB SoC controller + +maintainers: + - Manish Narani <manish.narani@xilinx.com> + +properties: + compatible: + items: + - enum: + - xlnx,zynqmp-dwc3 + - xlnx,versal-dwc3 + reg: + maxItems: 1 + + "#address-cells": + enum: [ 1, 2 ] + + "#size-cells": + enum: [ 1, 2 ] + + ranges: true + + power-domains: + description: specifies a phandle to PM domain provider node + maxItems: 1 + + clocks: + description: + A list of phandle and clock-specifier pairs for the clocks + listed in clock-names. + items: + - description: Master/Core clock, has to be >= 125 MHz + for SS operation and >= 60MHz for HS operation. + - description: Clock source to core during PHY power down. + + clock-names: + items: + - const: bus_clk + - const: ref_clk + + resets: + description: + A list of phandles for resets listed in reset-names. + + items: + - description: USB core reset + - description: USB hibernation reset + - description: USB APB reset + + reset-names: + items: + - const: usb_crst + - const: usb_hibrst + - const: usb_apbrst + + phys: + minItems: 1 + maxItems: 2 + + phy-names: + minItems: 1 + maxItems: 2 + items: + enum: + - usb2-phy + - usb3-phy + +# Required child node: + +patternProperties: + "^usb@[0-9a-f]+$": + $ref: snps,dwc3.yaml# + +required: + - compatible + - reg + - "#address-cells" + - "#size-cells" + - ranges + - power-domains + - clocks + - clock-names + - resets + - reset-names + +additionalProperties: false + +examples: + - | + #include <dt-bindings/dma/xlnx-zynqmp-dpdma.h> + #include <dt-bindings/power/xlnx-zynqmp-power.h> + #include <dt-bindings/reset/xlnx-zynqmp-resets.h> + #include <dt-bindings/clock/xlnx-zynqmp-clk.h> + #include <dt-bindings/reset/xlnx-zynqmp-resets.h> + #include <dt-bindings/phy/phy.h> + axi { + #address-cells = <2>; + #size-cells = <2>; + + usb@0 { + #address-cells = <0x2>; + #size-cells = <0x2>; + compatible = "xlnx,zynqmp-dwc3"; + reg = <0x0 0xff9d0000 0x0 0x100>; + clocks = <&zynqmp_clk USB0_BUS_REF>, <&zynqmp_clk USB3_DUAL_REF>; + clock-names = "bus_clk", "ref_clk"; + power-domains = <&zynqmp_firmware PD_USB_0>; + resets = <&zynqmp_reset ZYNQMP_RESET_USB1_CORERESET>, + <&zynqmp_reset ZYNQMP_RESET_USB1_HIBERRESET>, + <&zynqmp_reset ZYNQMP_RESET_USB1_APB>; + reset-names = "usb_crst", "usb_hibrst", "usb_apbrst"; + phys = <&psgtr 2 PHY_TYPE_USB3 0 2>; + phy-names = "usb3-phy"; + ranges; + + usb@fe200000 { + compatible = "snps,dwc3"; + reg = <0x0 0xfe200000 0x0 0x40000>; + interrupt-names = "host", "otg"; + interrupts = <0 65 4>, <0 69 4>; + dr_mode = "host"; + dma-coherent; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.yaml b/Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.yaml index 8428415896ce..a39c76b89484 100644 --- a/Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.yaml +++ b/Documentation/devicetree/bindings/usb/nvidia,tegra-xudc.yaml @@ -59,6 +59,19 @@ properties: - const: fs_src - const: hs_src + interconnects: + items: + - description: memory read client + - description: memory write client + + interconnect-names: + items: + - const: dma-mem # read + - const: write + + iommus: + maxItems: 1 + power-domains: items: - description: XUSBB(device) power-domain diff --git a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml index 2bdaba023c01..2d23a4ff702f 100644 --- a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml @@ -13,7 +13,9 @@ properties: compatible: items: - enum: + - qcom,ipq4019-dwc3 - qcom,ipq6018-dwc3 + - qcom,ipq8064-dwc3 - qcom,msm8996-dwc3 - qcom,msm8998-dwc3 - qcom,sc7180-dwc3 @@ -23,9 +25,11 @@ properties: - qcom,sdx55-dwc3 - qcom,sm4250-dwc3 - qcom,sm6115-dwc3 + - qcom,sm6350-dwc3 - qcom,sm8150-dwc3 - qcom,sm8250-dwc3 - qcom,sm8350-dwc3 + - qcom,sm8450-dwc3 - const: qcom,dwc3 reg: diff --git a/Documentation/driver-api/usb/writing_usb_driver.rst b/Documentation/driver-api/usb/writing_usb_driver.rst index b43e1ce49f0e..95c4f5d14052 100644 --- a/Documentation/driver-api/usb/writing_usb_driver.rst +++ b/Documentation/driver-api/usb/writing_usb_driver.rst @@ -94,8 +94,8 @@ usually in the driver's init function, as shown here:: /* register this driver with the USB subsystem */ result = usb_register(&skel_driver); if (result < 0) { - err("usb_register failed for the "__FILE__ "driver." - "Error number %d", result); + pr_err("usb_register failed for the %s driver. Error number %d\n", + skel_driver.name, result); return -1; } @@ -170,8 +170,8 @@ structure. This is done so that future calls to file operations will enable the driver to determine which device the user is addressing. All of this is done with the following code:: - /* increment our usage count for the module */ - ++skel->open_count; + /* increment our usage count for the device */ + kref_get(&dev->kref); /* save our object in the file's private structure */ file->private_data = dev; @@ -188,24 +188,26 @@ space, points the urb to the data and submits the urb to the USB subsystem. This can be seen in the following code:: /* we can only write as much as 1 urb will hold */ - bytes_written = (count > skel->bulk_out_size) ? skel->bulk_out_size : count; + size_t writesize = min_t(size_t, count, MAX_TRANSFER); /* copy the data from user space into our urb */ - copy_from_user(skel->write_urb->transfer_buffer, buffer, bytes_written); + copy_from_user(buf, user_buffer, writesize); /* set up our urb */ - usb_fill_bulk_urb(skel->write_urb, - skel->dev, - usb_sndbulkpipe(skel->dev, skel->bulk_out_endpointAddr), - skel->write_urb->transfer_buffer, - bytes_written, + usb_fill_bulk_urb(urb, + dev->udev, + usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr), + buf, + writesize, skel_write_bulk_callback, - skel); + dev); /* send the data out the bulk port */ - result = usb_submit_urb(skel->write_urb); - if (result) { - err("Failed submitting write urb, error %d", result); + retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval) { + dev_err(&dev->interface->dev, + "%s - failed submitting write urb, error %d\n", + __func__, retval); } diff --git a/MAINTAINERS b/MAINTAINERS index 8912b2c1260c..577c18514a4a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20859,6 +20859,14 @@ F: drivers/scsi/xen-scsifront.c F: drivers/xen/xen-scsiback.c F: include/xen/interface/io/vscsiif.h +XEN PVUSB DRIVER +M: Juergen Gross <jgross@suse.com> +L: xen-devel@lists.xenproject.org (moderated for non-subscribers) +L: linux-usb@vger.kernel.org +S: Supported +F: drivers/usb/host/xen* +F: include/xen/interface/io/usbif.h + XEN SOUND FRONTEND DRIVER M: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com> L: xen-devel@lists.xenproject.org (moderated for non-subscribers) diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c index e85bf768c66d..5c9d07cc5410 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.c +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -81,7 +81,7 @@ int cdnsp_find_next_ext_cap(void __iomem *base, u32 start, int id) offset = HCC_EXT_CAPS(val) << 2; if (!offset) return 0; - }; + } do { val = readl(base + offset); diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index a56f06368d14..5359b2a2e4d2 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -864,6 +864,7 @@ struct platform_device *ci_hdrc_add_device(struct device *dev, } pdev->dev.parent = dev; + device_set_of_node_from_dev(&pdev->dev, dev); ret = platform_device_add_resources(pdev, res, nres); if (ret) diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index 8dd59282827b..7b53274ef966 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -255,10 +255,9 @@ int ci_hdrc_otg_init(struct ci_hdrc *ci) */ void ci_hdrc_otg_destroy(struct ci_hdrc *ci) { - if (ci->wq) { - flush_workqueue(ci->wq); + if (ci->wq) destroy_workqueue(ci->wq); - } + /* Disable all OTG irq and clear status */ hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, OTGSC_INT_STATUS_BITS); diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 072968c40ade..355ed33a2179 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * drivers/usb/driver.c - most of the driver model stuff for usb + * drivers/usb/core/driver.c - most of the driver model stuff for usb * * (C) Copyright 2005 Greg Kroah-Hartman <gregkh@suse.de> * @@ -834,6 +834,7 @@ const struct usb_device_id *usb_device_match_id(struct usb_device *udev, return NULL; } +EXPORT_SYMBOL_GPL(usb_device_match_id); bool usb_driver_applicable(struct usb_device *udev, struct usb_device_driver *udrv) diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c index 26f9fb9f67ca..740342a2812a 100644 --- a/drivers/usb/core/generic.c +++ b/drivers/usb/core/generic.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * drivers/usb/generic.c - generic driver for USB devices (not interfaces) + * drivers/usb/core/generic.c - generic driver for USB devices (not interfaces) * * (C) Copyright 2005 Greg Kroah-Hartman <gregkh@suse.de> * diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 4d326ee12c36..9ffc63ae65ac 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1281,7 +1281,7 @@ static int hcd_alloc_coherent(struct usb_bus *bus, return -EFAULT; } - vaddr = hcd_buffer_alloc(bus, size + sizeof(vaddr), + vaddr = hcd_buffer_alloc(bus, size + sizeof(unsigned long), mem_flags, dma_handle); if (!vaddr) return -ENOMEM; diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 00070a8a6507..721794f0f494 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1110,7 +1110,10 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) } else { hub_power_on(hub, true); } - } + /* Give some time on remote wakeup to let links to transit to U0 */ + } else if (hub_is_superspeed(hub->hdev)) + msleep(20); + init2: /* @@ -2777,6 +2780,8 @@ static unsigned hub_is_wusb(struct usb_hub *hub) #define PORT_INIT_TRIES 4 #endif /* CONFIG_USB_FEW_INIT_RETRIES */ +#define DETECT_DISCONNECT_TRIES 5 + #define HUB_ROOT_RESET_TIME 60 /* times are in msec */ #define HUB_SHORT_RESET_TIME 10 #define HUB_BH_RESET_TIME 50 @@ -3570,7 +3575,7 @@ static int finish_port_resume(struct usb_device *udev) * This routine should only be called when persist is enabled. */ static int wait_for_connected(struct usb_device *udev, - struct usb_hub *hub, int *port1, + struct usb_hub *hub, int port1, u16 *portchange, u16 *portstatus) { int status = 0, delay_ms = 0; @@ -3584,7 +3589,7 @@ static int wait_for_connected(struct usb_device *udev, } msleep(20); delay_ms += 20; - status = hub_port_status(hub, *port1, portstatus, portchange); + status = hub_port_status(hub, port1, portstatus, portchange); } dev_dbg(&udev->dev, "Waited %dms for CONNECT\n", delay_ms); return status; @@ -3690,7 +3695,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) } if (udev->persist_enabled) - status = wait_for_connected(udev, hub, &port1, &portchange, + status = wait_for_connected(udev, hub, port1, &portchange, &portstatus); status = check_port_resume_type(udev, @@ -5543,6 +5548,7 @@ static void port_event(struct usb_hub *hub, int port1) struct usb_device *udev = port_dev->child; struct usb_device *hdev = hub->hdev; u16 portstatus, portchange; + int i = 0; connect_change = test_bit(port1, hub->change_bits); clear_bit(port1, hub->event_bits); @@ -5619,17 +5625,27 @@ static void port_event(struct usb_hub *hub, int port1) connect_change = 1; /* - * Warm reset a USB3 protocol port if it's in - * SS.Inactive state. + * Avoid trying to recover a USB3 SS.Inactive port with a warm reset if + * the device was disconnected. A 12ms disconnect detect timer in + * SS.Inactive state transitions the port to RxDetect automatically. + * SS.Inactive link error state is common during device disconnect. */ - if (hub_port_warm_reset_required(hub, port1, portstatus)) { - dev_dbg(&port_dev->dev, "do warm reset\n"); - if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION) + while (hub_port_warm_reset_required(hub, port1, portstatus)) { + if ((i++ < DETECT_DISCONNECT_TRIES) && udev) { + u16 unused; + + msleep(20); + hub_port_status(hub, port1, &portstatus, &unused); + dev_dbg(&port_dev->dev, "Wait for inactive link disconnect detect\n"); + continue; + } else if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION) || udev->state == USB_STATE_NOTATTACHED) { + dev_dbg(&port_dev->dev, "do warm reset, port only\n"); if (hub_port_reset(hub, port1, NULL, HUB_BH_RESET_TIME, true) < 0) hub_port_disable(hub, port1, 1); } else { + dev_dbg(&port_dev->dev, "do warm reset, full device\n"); usb_unlock_port(port_dev); usb_lock_device(udev); usb_reset_device(udev); @@ -5637,6 +5653,7 @@ static void port_event(struct usb_hub *hub, int port1) usb_lock_port(port_dev); connect_change = 0; } + break; } if (connect_change) diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 37185eb66ae4..e2fe64239328 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -869,6 +869,8 @@ struct dwc2_hregs_backup { * - USB_DR_MODE_HOST * - USB_DR_MODE_OTG * @role_sw: usb_role_switch handle + * @role_sw_default_mode: default operation mode of controller while usb role + * is USB_ROLE_NONE * @hcd_enabled: Host mode sub-driver initialization indicator. * @gadget_enabled: Peripheral mode sub-driver initialization indicator. * @ll_hw_enabled: Status of low-level hardware resources. @@ -1065,6 +1067,7 @@ struct dwc2_hsotg { enum usb_otg_state op_state; enum usb_dr_mode dr_mode; struct usb_role_switch *role_sw; + enum usb_dr_mode role_sw_default_mode; unsigned int hcd_enabled:1; unsigned int gadget_enabled:1; unsigned int ll_hw_enabled:1; diff --git a/drivers/usb/dwc2/drd.c b/drivers/usb/dwc2/drd.c index aa6eb76f64dd..1b39c4776369 100644 --- a/drivers/usb/dwc2/drd.c +++ b/drivers/usb/dwc2/drd.c @@ -13,6 +13,10 @@ #include <linux/usb/role.h> #include "core.h" +#define dwc2_ovr_gotgctl(gotgctl) \ + ((gotgctl) |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | GOTGCTL_VBVALOEN | \ + GOTGCTL_DBNCE_FLTR_BYPASS) + static void dwc2_ovr_init(struct dwc2_hsotg *hsotg) { unsigned long flags; @@ -21,9 +25,12 @@ static void dwc2_ovr_init(struct dwc2_hsotg *hsotg) spin_lock_irqsave(&hsotg->lock, flags); gotgctl = dwc2_readl(hsotg, GOTGCTL); - gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | GOTGCTL_VBVALOEN; - gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS; + dwc2_ovr_gotgctl(gotgctl); gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL); + if (hsotg->role_sw_default_mode == USB_DR_MODE_HOST) + gotgctl |= GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL; + else if (hsotg->role_sw_default_mode == USB_DR_MODE_PERIPHERAL) + gotgctl |= GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL; dwc2_writel(hsotg, gotgctl, GOTGCTL); spin_unlock_irqrestore(&hsotg->lock, flags); @@ -40,6 +47,9 @@ static int dwc2_ovr_avalid(struct dwc2_hsotg *hsotg, bool valid) (!valid && !(gotgctl & GOTGCTL_ASESVLD))) return -EALREADY; + /* Always enable overrides to handle the resume case */ + dwc2_ovr_gotgctl(gotgctl); + gotgctl &= ~GOTGCTL_BVALOVAL; if (valid) gotgctl |= GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL; @@ -59,6 +69,9 @@ static int dwc2_ovr_bvalid(struct dwc2_hsotg *hsotg, bool valid) (!valid && !(gotgctl & GOTGCTL_BSESVLD))) return -EALREADY; + /* Always enable overrides to handle the resume case */ + dwc2_ovr_gotgctl(gotgctl); + gotgctl &= ~GOTGCTL_AVALOVAL; if (valid) gotgctl |= GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL; @@ -105,6 +118,14 @@ static int dwc2_drd_role_sw_set(struct usb_role_switch *sw, enum usb_role role) spin_lock_irqsave(&hsotg->lock, flags); + if (role == USB_ROLE_NONE) { + /* default operation mode when usb role is USB_ROLE_NONE */ + if (hsotg->role_sw_default_mode == USB_DR_MODE_HOST) + role = USB_ROLE_HOST; + else if (hsotg->role_sw_default_mode == USB_DR_MODE_PERIPHERAL) + role = USB_ROLE_DEVICE; + } + if (role == USB_ROLE_HOST) { already = dwc2_ovr_avalid(hsotg, true); } else if (role == USB_ROLE_DEVICE) { @@ -146,6 +167,7 @@ int dwc2_drd_init(struct dwc2_hsotg *hsotg) if (!device_property_read_bool(hsotg->dev, "usb-role-switch")) return 0; + hsotg->role_sw_default_mode = usb_get_role_switch_default_mode(hsotg->dev); role_sw_desc.driver_data = hsotg; role_sw_desc.fwnode = dev_fwnode(hsotg->dev); role_sw_desc.set = dwc2_drd_role_sw_set; @@ -183,6 +205,31 @@ void dwc2_drd_suspend(struct dwc2_hsotg *hsotg) void dwc2_drd_resume(struct dwc2_hsotg *hsotg) { u32 gintsts, gintmsk; + enum usb_role role; + + if (hsotg->role_sw) { + /* get last known role (as the get ops isn't implemented by this driver) */ + role = usb_role_switch_get_role(hsotg->role_sw); + + if (role == USB_ROLE_NONE) { + if (hsotg->role_sw_default_mode == USB_DR_MODE_HOST) + role = USB_ROLE_HOST; + else if (hsotg->role_sw_default_mode == USB_DR_MODE_PERIPHERAL) + role = USB_ROLE_DEVICE; + } + + /* restore last role that may have been lost */ + if (role == USB_ROLE_HOST) + dwc2_ovr_avalid(hsotg, true); + else if (role == USB_ROLE_DEVICE) + dwc2_ovr_bvalid(hsotg, true); + + dwc2_force_mode(hsotg, role == USB_ROLE_HOST); + + dev_dbg(hsotg->dev, "resuming %s-session valid\n", + role == USB_ROLE_NONE ? "No" : + role == USB_ROLE_HOST ? "A" : "B"); + } if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) { gintsts = dwc2_readl(hsotg, GINTSTS); diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index ab8d7dad9f56..b884a83b26a6 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -5217,7 +5217,7 @@ int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg, int remote_wakeup) * as result BNA interrupt asserted on hibernation exit * by restoring from saved area. */ - if (hsotg->params.g_dma_desc && + if (using_desc_dma(hsotg) && (dr->diepctl[i] & DXEPCTL_EPENA)) dr->diepdma[i] = hsotg->eps_in[i]->desc_list_dma; dwc2_writel(hsotg, dr->dtxfsiz[i], DPTXFSIZN(i)); @@ -5229,7 +5229,7 @@ int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg, int remote_wakeup) * as result BNA interrupt asserted on hibernation exit * by restoring from saved area. */ - if (hsotg->params.g_dma_desc && + if (using_desc_dma(hsotg) && (dr->doepctl[i] & DXEPCTL_EPENA)) dr->doepdma[i] = hsotg->eps_out[i]->desc_list_dma; dwc2_writel(hsotg, dr->doepdma[i], DOEPDMA(i)); diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index c331a5128c2c..c8ba87df7abe 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -222,20 +222,16 @@ static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg) int i, ret; hsotg->reset = devm_reset_control_get_optional(hsotg->dev, "dwc2"); - if (IS_ERR(hsotg->reset)) { - ret = PTR_ERR(hsotg->reset); - dev_err(hsotg->dev, "error getting reset control %d\n", ret); - return ret; - } + if (IS_ERR(hsotg->reset)) + return dev_err_probe(hsotg->dev, PTR_ERR(hsotg->reset), + "error getting reset control\n"); reset_control_deassert(hsotg->reset); hsotg->reset_ecc = devm_reset_control_get_optional(hsotg->dev, "dwc2-ecc"); - if (IS_ERR(hsotg->reset_ecc)) { - ret = PTR_ERR(hsotg->reset_ecc); - dev_err(hsotg->dev, "error getting reset control for ecc %d\n", ret); - return ret; - } + if (IS_ERR(hsotg->reset_ecc)) + return dev_err_probe(hsotg->dev, PTR_ERR(hsotg->reset_ecc), + "error getting reset control for ecc\n"); reset_control_deassert(hsotg->reset_ecc); @@ -251,11 +247,8 @@ static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg) case -ENOSYS: hsotg->phy = NULL; break; - case -EPROBE_DEFER: - return ret; default: - dev_err(hsotg->dev, "error getting phy %d\n", ret); - return ret; + return dev_err_probe(hsotg->dev, ret, "error getting phy\n"); } } @@ -268,12 +261,8 @@ static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg) case -ENXIO: hsotg->uphy = NULL; break; - case -EPROBE_DEFER: - return ret; default: - dev_err(hsotg->dev, "error getting usb phy %d\n", - ret); - return ret; + return dev_err_probe(hsotg->dev, ret, "error getting usb phy\n"); } } } @@ -282,10 +271,8 @@ static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg) /* Clock */ hsotg->clk = devm_clk_get_optional(hsotg->dev, "otg"); - if (IS_ERR(hsotg->clk)) { - dev_err(hsotg->dev, "cannot get otg clock\n"); - return PTR_ERR(hsotg->clk); - } + if (IS_ERR(hsotg->clk)) + return dev_err_probe(hsotg->dev, PTR_ERR(hsotg->clk), "cannot get otg clock\n"); /* Regulators */ for (i = 0; i < ARRAY_SIZE(hsotg->supplies); i++) @@ -293,12 +280,9 @@ static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg) ret = devm_regulator_bulk_get(hsotg->dev, ARRAY_SIZE(hsotg->supplies), hsotg->supplies); - if (ret) { - if (ret != -EPROBE_DEFER) - dev_err(hsotg->dev, "failed to request supplies: %d\n", - ret); - return ret; - } + if (ret) + return dev_err_probe(hsotg->dev, ret, "failed to request supplies\n"); + return 0; } @@ -558,16 +542,12 @@ static int dwc2_driver_probe(struct platform_device *dev) hsotg->usb33d = devm_regulator_get(hsotg->dev, "usb33d"); if (IS_ERR(hsotg->usb33d)) { retval = PTR_ERR(hsotg->usb33d); - if (retval != -EPROBE_DEFER) - dev_err(hsotg->dev, - "failed to request usb33d supply: %d\n", - retval); + dev_err_probe(hsotg->dev, retval, "failed to request usb33d supply\n"); goto error; } retval = regulator_enable(hsotg->usb33d); if (retval) { - dev_err(hsotg->dev, - "failed to enable usb33d supply: %d\n", retval); + dev_err_probe(hsotg->dev, retval, "failed to enable usb33d supply\n"); goto error; } @@ -582,8 +562,7 @@ static int dwc2_driver_probe(struct platform_device *dev) retval = dwc2_drd_init(hsotg); if (retval) { - if (retval != -EPROBE_DEFER) - dev_err(hsotg->dev, "failed to initialize dual-role\n"); + dev_err_probe(hsotg->dev, retval, "failed to initialize dual-role\n"); goto error_init; } @@ -751,10 +730,12 @@ static int __maybe_unused dwc2_resume(struct device *dev) spin_unlock_irqrestore(&dwc2->lock, flags); } - /* Need to restore FORCEDEVMODE/FORCEHOSTMODE */ - dwc2_force_dr_mode(dwc2); - - dwc2_drd_resume(dwc2); + if (!dwc2->role_sw) { + /* Need to restore FORCEDEVMODE/FORCEHOSTMODE */ + dwc2_force_dr_mode(dwc2); + } else { + dwc2_drd_resume(dwc2); + } if (dwc2_is_device_mode(dwc2)) ret = dwc2_hsotg_resume(dwc2); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 5c491d0a19d7..e1cc3f7398fb 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -153,6 +153,7 @@ #define DWC3_DGCMDPAR 0xc710 #define DWC3_DGCMD 0xc714 #define DWC3_DALEPENA 0xc720 +#define DWC3_DCFG1 0xc740 /* DWC_usb32 only */ #define DWC3_DEP_BASE(n) (0xc800 + ((n) * 0x10)) #define DWC3_DEPCMDPAR2 0x00 @@ -382,6 +383,7 @@ /* Global HWPARAMS9 Register */ #define DWC3_GHWPARAMS9_DEV_TXF_FLUSH_BYPASS BIT(0) +#define DWC3_GHWPARAMS9_DEV_MST BIT(1) /* Global Frame Length Adjustment Register */ #define DWC3_GFLADJ_30MHZ_SDBND_SEL BIT(7) @@ -558,6 +560,9 @@ /* The EP number goes 0..31 so ep0 is always out and ep1 is always in */ #define DWC3_DALEPENA_EP(n) BIT(n) +/* DWC_usb32 DCFG1 config */ +#define DWC3_DCFG1_DIS_MST_ENH BIT(1) + #define DWC3_DEPCMD_TYPE_CONTROL 0 #define DWC3_DEPCMD_TYPE_ISOC 1 #define DWC3_DEPCMD_TYPE_BULK 2 @@ -888,6 +893,10 @@ struct dwc3_hwparams { /* HWPARAMS7 */ #define DWC3_RAM1_DEPTH(n) ((n) & 0xffff) +/* HWPARAMS9 */ +#define DWC3_MST_CAPABLE(p) (!!((p)->hwparams9 & \ + DWC3_GHWPARAMS9_DEV_MST)) + /** * struct dwc3_request - representation of a transfer request * @request: struct usb_request to be transferred diff --git a/drivers/usb/dwc3/dwc3-meson-g12a.c b/drivers/usb/dwc3/dwc3-meson-g12a.c index d0f9b7c296b0..bd814df3bf8b 100644 --- a/drivers/usb/dwc3/dwc3-meson-g12a.c +++ b/drivers/usb/dwc3/dwc3-meson-g12a.c @@ -755,16 +755,16 @@ static int dwc3_meson_g12a_probe(struct platform_device *pdev) ret = dwc3_meson_g12a_get_phys(priv); if (ret) - goto err_disable_clks; + goto err_rearm; ret = priv->drvdata->setup_regmaps(priv, base); if (ret) - goto err_disable_clks; + goto err_rearm; if (priv->vbus) { ret = regulator_enable(priv->vbus); if (ret) - goto err_disable_clks; + goto err_rearm; } /* Get dr_mode */ @@ -825,6 +825,9 @@ err_disable_regulator: if (priv->vbus) regulator_disable(priv->vbus); +err_rearm: + reset_control_rearm(priv->reset); + err_disable_clks: clk_bulk_disable_unprepare(priv->drvdata->num_clks, priv->drvdata->clks); @@ -852,6 +855,8 @@ static int dwc3_meson_g12a_remove(struct platform_device *pdev) pm_runtime_put_noidle(dev); pm_runtime_set_suspended(dev); + reset_control_rearm(priv->reset); + clk_bulk_disable_unprepare(priv->drvdata->num_clks, priv->drvdata->clks); @@ -892,7 +897,7 @@ static int __maybe_unused dwc3_meson_g12a_suspend(struct device *dev) phy_exit(priv->phys[i]); } - reset_control_assert(priv->reset); + reset_control_rearm(priv->reset); return 0; } @@ -902,7 +907,9 @@ static int __maybe_unused dwc3_meson_g12a_resume(struct device *dev) struct dwc3_meson_g12a *priv = dev_get_drvdata(dev); int i, ret; - reset_control_deassert(priv->reset); + ret = reset_control_reset(priv->reset); + if (ret) + return ret; ret = priv->drvdata->usb_init(priv); if (ret) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 7e3db00e9759..520031ba38aa 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -331,9 +331,17 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd, } } - dwc3_writel(dep->regs, DWC3_DEPCMDPAR0, params->param0); - dwc3_writel(dep->regs, DWC3_DEPCMDPAR1, params->param1); - dwc3_writel(dep->regs, DWC3_DEPCMDPAR2, params->param2); + /* + * For some commands such as Update Transfer command, DEPCMDPARn + * registers are reserved. Since the driver often sends Update Transfer + * command, don't write to DEPCMDPARn to avoid register write delays and + * improve performance. + */ + if (DWC3_DEPCMD_CMD(cmd) != DWC3_DEPCMD_UPDATETRANSFER) { + dwc3_writel(dep->regs, DWC3_DEPCMDPAR0, params->param0); + dwc3_writel(dep->regs, DWC3_DEPCMDPAR1, params->param1); + dwc3_writel(dep->regs, DWC3_DEPCMDPAR2, params->param2); + } /* * Synopsys Databook 2.60a states in section 6.3.2.5.6 of that if we're @@ -357,6 +365,12 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd, cmd |= DWC3_DEPCMD_CMDACT; dwc3_writel(dep->regs, DWC3_DEPCMD, cmd); + + if (!(cmd & DWC3_DEPCMD_CMDACT)) { + ret = 0; + goto skip_status; + } + do { reg = dwc3_readl(dep->regs, DWC3_DEPCMD); if (!(reg & DWC3_DEPCMD_CMDACT)) { @@ -398,6 +412,7 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd, cmd_status = -ETIMEDOUT; } +skip_status: trace_dwc3_gadget_ep_cmd(dep, cmd, params, cmd_status); if (DWC3_DEPCMD_CMD(cmd) == DWC3_DEPCMD_STARTTRANSFER) { @@ -1260,12 +1275,17 @@ static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb, trb->ctrl |= DWC3_TRB_CTRL_ISP_IMI; } + /* All TRBs setup for MST must set CSP=1 when LST=0 */ + if (dep->stream_capable && DWC3_MST_CAPABLE(&dwc->hwparams)) + trb->ctrl |= DWC3_TRB_CTRL_CSP; + if ((!no_interrupt && !chain) || must_interrupt) trb->ctrl |= DWC3_TRB_CTRL_IOC; if (chain) trb->ctrl |= DWC3_TRB_CTRL_CHN; - else if (dep->stream_capable && is_last) + else if (dep->stream_capable && is_last && + !DWC3_MST_CAPABLE(&dwc->hwparams)) trb->ctrl |= DWC3_TRB_CTRL_LST; if (usb_endpoint_xfer_bulk(dep->endpoint.desc) && dep->stream_capable) @@ -1513,7 +1533,8 @@ static int dwc3_prepare_trbs(struct dwc3_ep *dep) * burst capability may try to read and use TRBs beyond the * active transfer instead of stopping. */ - if (dep->stream_capable && req->request.is_last) + if (dep->stream_capable && req->request.is_last && + !DWC3_MST_CAPABLE(&dep->dwc->hwparams)) return ret; } @@ -1546,7 +1567,8 @@ static int dwc3_prepare_trbs(struct dwc3_ep *dep) * burst capability may try to read and use TRBs beyond the * active transfer instead of stopping. */ - if (dep->stream_capable && req->request.is_last) + if (dep->stream_capable && req->request.is_last && + !DWC3_MST_CAPABLE(&dwc->hwparams)) return ret; } @@ -1623,7 +1645,8 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep) return ret; } - if (dep->stream_capable && req->request.is_last) + if (dep->stream_capable && req->request.is_last && + !DWC3_MST_CAPABLE(&dep->dwc->hwparams)) dep->flags |= DWC3_EP_WAIT_TRANSFER_COMPLETE; return 0; @@ -2638,6 +2661,13 @@ static int __dwc3_gadget_start(struct dwc3 *dwc) reg |= DWC3_DCFG_IGNSTRMPP; dwc3_writel(dwc->regs, DWC3_DCFG, reg); + /* Enable MST by default if the device is capable of MST */ + if (DWC3_MST_CAPABLE(&dwc->hwparams)) { + reg = dwc3_readl(dwc->regs, DWC3_DCFG1); + reg &= ~DWC3_DCFG1_DIS_MST_ENH; + dwc3_writel(dwc->regs, DWC3_DCFG1, reg); + } + /* Start with SuperSpeed Default */ dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); @@ -3437,7 +3467,8 @@ static void dwc3_gadget_endpoint_stream_event(struct dwc3_ep *dep, case DEPEVT_STREAM_NOSTREAM: if ((dep->flags & DWC3_EP_IGNORE_NEXT_NOSTREAM) || !(dep->flags & DWC3_EP_FORCE_RESTART_STREAM) || - !(dep->flags & DWC3_EP_WAIT_TRANSFER_COMPLETE)) + (!DWC3_MST_CAPABLE(&dwc->hwparams) && + !(dep->flags & DWC3_EP_WAIT_TRANSFER_COMPLETE))) break; /* @@ -4067,7 +4098,6 @@ static irqreturn_t dwc3_process_event_buf(struct dwc3_event_buffer *evt) struct dwc3 *dwc = evt->dwc; irqreturn_t ret = IRQ_NONE; int left; - u32 reg; left = evt->count; @@ -4099,9 +4129,8 @@ static irqreturn_t dwc3_process_event_buf(struct dwc3_event_buffer *evt) ret = IRQ_HANDLED; /* Unmask interrupt */ - reg = dwc3_readl(dwc->regs, DWC3_GEVNTSIZ(0)); - reg &= ~DWC3_GEVNTSIZ_INTMASK; - dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(0), reg); + dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(0), + DWC3_GEVNTSIZ_SIZE(evt->length)); if (dwc->imod_interval) { dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), DWC3_GEVNTCOUNT_EHB); @@ -4130,7 +4159,6 @@ static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) struct dwc3 *dwc = evt->dwc; u32 amount; u32 count; - u32 reg; if (pm_runtime_suspended(dwc->dev)) { pm_runtime_get(dwc->dev); @@ -4157,9 +4185,8 @@ static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) evt->flags |= DWC3_EVENT_PENDING; /* Mask interrupt */ - reg = dwc3_readl(dwc->regs, DWC3_GEVNTSIZ(0)); - reg |= DWC3_GEVNTSIZ_INTMASK; - dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(0), reg); + dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(0), + DWC3_GEVNTSIZ_INTMASK | DWC3_GEVNTSIZ_SIZE(evt->length)); amount = min(count, evt->length - evt->lpos); memcpy(evt->cache + evt->lpos, evt->buf + evt->lpos, amount); diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 3789c329183c..16f9e3423c9f 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -159,6 +159,8 @@ int config_ep_by_speed_and_alt(struct usb_gadget *g, int want_comp_desc = 0; struct usb_descriptor_header **d_spd; /* cursor for speed desc */ + struct usb_composite_dev *cdev; + bool incomplete_desc = false; if (!g || !f || !_ep) return -EIO; @@ -167,28 +169,43 @@ int config_ep_by_speed_and_alt(struct usb_gadget *g, switch (g->speed) { case USB_SPEED_SUPER_PLUS: if (gadget_is_superspeed_plus(g)) { - speed_desc = f->ssp_descriptors; - want_comp_desc = 1; - break; + if (f->ssp_descriptors) { + speed_desc = f->ssp_descriptors; + want_comp_desc = 1; + break; + } + incomplete_desc = true; } fallthrough; case USB_SPEED_SUPER: if (gadget_is_superspeed(g)) { - speed_desc = f->ss_descriptors; - want_comp_desc = 1; - break; + if (f->ss_descriptors) { + speed_desc = f->ss_descriptors; + want_comp_desc = 1; + break; + } + incomplete_desc = true; } fallthrough; case USB_SPEED_HIGH: if (gadget_is_dualspeed(g)) { - speed_desc = f->hs_descriptors; - break; + if (f->hs_descriptors) { + speed_desc = f->hs_descriptors; + break; + } + incomplete_desc = true; } fallthrough; default: speed_desc = f->fs_descriptors; } + cdev = get_gadget_data(g); + if (incomplete_desc) + WARNING(cdev, + "%s doesn't hold the descriptors for current speed\n", + f->name); + /* find correct alternate setting descriptor */ for_each_desc(speed_desc, d_spd, USB_DT_INTERFACE) { int_desc = (struct usb_interface_descriptor *)*d_spd; @@ -244,12 +261,8 @@ ep_found: _ep->maxburst = comp_desc->bMaxBurst + 1; break; default: - if (comp_desc->bMaxBurst != 0) { - struct usb_composite_dev *cdev; - - cdev = get_gadget_data(g); + if (comp_desc->bMaxBurst != 0) ERROR(cdev, "ep0 bMaxBurst must be 0\n"); - } _ep->maxburst = 1; break; } diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 36c611d1d8d0..d4a678c0806e 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -89,10 +89,6 @@ struct gadget_strings { struct list_head list; }; -struct os_desc { - struct config_group group; -}; - struct gadget_config_name { struct usb_gadget_strings stringtab_dev; struct usb_string strings; @@ -420,9 +416,8 @@ static int config_usb_cfg_link( struct config_usb_cfg *cfg = to_config_usb_cfg(usb_cfg_ci); struct gadget_info *gi = cfg_to_gadget_info(cfg); - struct config_group *group = to_config_group(usb_func_ci); - struct usb_function_instance *fi = container_of(group, - struct usb_function_instance, group); + struct usb_function_instance *fi = + to_usb_function_instance(usb_func_ci); struct usb_function_instance *a_fi; struct usb_function *f; int ret; @@ -470,9 +465,8 @@ static void config_usb_cfg_unlink( struct config_usb_cfg *cfg = to_config_usb_cfg(usb_cfg_ci); struct gadget_info *gi = cfg_to_gadget_info(cfg); - struct config_group *group = to_config_group(usb_func_ci); - struct usb_function_instance *fi = container_of(group, - struct usb_function_instance, group); + struct usb_function_instance *fi = + to_usb_function_instance(usb_func_ci); struct usb_function *f; /* @@ -783,15 +777,11 @@ static void gadget_strings_attr_release(struct config_item *item) USB_CONFIG_STRING_RW_OPS(gadget_strings); USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info); -static inline struct os_desc *to_os_desc(struct config_item *item) -{ - return container_of(to_config_group(item), struct os_desc, group); -} - static inline struct gadget_info *os_desc_item_to_gadget_info( struct config_item *item) { - return to_gadget_info(to_os_desc(item)->group.cg_item.ci_parent); + return container_of(to_config_group(item), + struct gadget_info, os_desc_group); } static ssize_t os_desc_use_show(struct config_item *item, char *page) @@ -886,21 +876,12 @@ static struct configfs_attribute *os_desc_attrs[] = { NULL, }; -static void os_desc_attr_release(struct config_item *item) -{ - struct os_desc *os_desc = to_os_desc(item); - kfree(os_desc); -} - static int os_desc_link(struct config_item *os_desc_ci, struct config_item *usb_cfg_ci) { - struct gadget_info *gi = container_of(to_config_group(os_desc_ci), - struct gadget_info, os_desc_group); + struct gadget_info *gi = os_desc_item_to_gadget_info(os_desc_ci); struct usb_composite_dev *cdev = &gi->cdev; - struct config_usb_cfg *c_target = - container_of(to_config_group(usb_cfg_ci), - struct config_usb_cfg, group); + struct config_usb_cfg *c_target = to_config_usb_cfg(usb_cfg_ci); struct usb_configuration *c; int ret; @@ -930,8 +911,7 @@ out: static void os_desc_unlink(struct config_item *os_desc_ci, struct config_item *usb_cfg_ci) { - struct gadget_info *gi = container_of(to_config_group(os_desc_ci), - struct gadget_info, os_desc_group); + struct gadget_info *gi = os_desc_item_to_gadget_info(os_desc_ci); struct usb_composite_dev *cdev = &gi->cdev; mutex_lock(&gi->lock); @@ -943,7 +923,6 @@ static void os_desc_unlink(struct config_item *os_desc_ci, } static struct configfs_item_operations os_desc_ops = { - .release = os_desc_attr_release, .allow_link = os_desc_link, .drop_link = os_desc_unlink, }; diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index e20c19a0f106..3c584da9118c 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -614,7 +614,7 @@ static int ffs_ep0_open(struct inode *inode, struct file *file) file->private_data = ffs; ffs_data_opened(ffs); - return 0; + return stream_open(inode, file); } static int ffs_ep0_release(struct inode *inode, struct file *file) @@ -1154,7 +1154,7 @@ ffs_epfile_open(struct inode *inode, struct file *file) file->private_data = epfile; ffs_data_opened(epfile->ffs); - return 0; + return stream_open(inode, file); } static int ffs_aio_cancel(struct kiocb *kiocb) diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c index 71a1a26e85c7..fddf539008a9 100644 --- a/drivers/usb/gadget/function/f_midi.c +++ b/drivers/usb/gadget/function/f_midi.c @@ -1097,7 +1097,7 @@ static ssize_t f_midi_opts_##name##_show(struct config_item *item, char *page) \ int result; \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", opts->name); \ + result = sprintf(page, "%u\n", opts->name); \ mutex_unlock(&opts->lock); \ \ return result; \ @@ -1134,7 +1134,51 @@ end: \ \ CONFIGFS_ATTR(f_midi_opts_, name); -F_MIDI_OPT(index, true, SNDRV_CARDS); +#define F_MIDI_OPT_SIGNED(name, test_limit, limit) \ +static ssize_t f_midi_opts_##name##_show(struct config_item *item, char *page) \ +{ \ + struct f_midi_opts *opts = to_f_midi_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", opts->name); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_midi_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_midi_opts *opts = to_f_midi_opts(item); \ + int ret; \ + s32 num; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt > 1) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtos32(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (test_limit && num > limit) { \ + ret = -EINVAL; \ + goto end; \ + } \ + opts->name = num; \ + ret = len; \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_midi_opts_, name); + +F_MIDI_OPT_SIGNED(index, true, SNDRV_CARDS); F_MIDI_OPT(buflen, false, 0); F_MIDI_OPT(qlen, false, 0); F_MIDI_OPT(in_ports, true, MAX_PORTS); diff --git a/drivers/usb/gadget/udc/aspeed-vhub/dev.c b/drivers/usb/gadget/udc/aspeed-vhub/dev.c index d918e8b2af3c..b0dfca43fbdc 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/dev.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/dev.c @@ -110,15 +110,26 @@ static int ast_vhub_dev_feature(struct ast_vhub_dev *d, u16 wIndex, u16 wValue, bool is_set) { + u32 val; + DDBG(d, "%s_FEATURE(dev val=%02x)\n", is_set ? "SET" : "CLEAR", wValue); - if (wValue != USB_DEVICE_REMOTE_WAKEUP) - return std_req_driver; + if (wValue == USB_DEVICE_REMOTE_WAKEUP) { + d->wakeup_en = is_set; + return std_req_complete; + } - d->wakeup_en = is_set; + if (wValue == USB_DEVICE_TEST_MODE) { + val = readl(d->vhub->regs + AST_VHUB_CTRL); + val &= ~GENMASK(10, 8); + val |= VHUB_CTRL_SET_TEST_MODE((wIndex >> 8) & 0x7); + writel(val, d->vhub->regs + AST_VHUB_CTRL); - return std_req_complete; + return std_req_complete; + } + + return std_req_driver; } static int ast_vhub_ep_feature(struct ast_vhub_dev *d, diff --git a/drivers/usb/gadget/udc/aspeed-vhub/ep0.c b/drivers/usb/gadget/udc/aspeed-vhub/ep0.c index 74ea36c19b1e..b4cf46249fea 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/ep0.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/ep0.c @@ -251,6 +251,13 @@ static void ast_vhub_ep0_do_receive(struct ast_vhub_ep *ep, struct ast_vhub_req len = remain; rc = -EOVERFLOW; } + + /* Hardware return wrong data len */ + if (len < ep->ep.maxpacket && len != remain) { + EPDBG(ep, "using expected data len instead\n"); + len = remain; + } + if (len && req->req.buf) memcpy(req->req.buf + req->req.actual, ep->buf, len); req->req.actual += len; diff --git a/drivers/usb/gadget/udc/aspeed-vhub/hub.c b/drivers/usb/gadget/udc/aspeed-vhub/hub.c index b9960fdd8a51..65cd4e46f031 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/hub.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c @@ -68,6 +68,18 @@ static const struct usb_device_descriptor ast_vhub_dev_desc = { .bNumConfigurations = 1, }; +static const struct usb_qualifier_descriptor ast_vhub_qual_desc = { + .bLength = 0xA, + .bDescriptorType = USB_DT_DEVICE_QUALIFIER, + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_HUB, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .bRESERVED = 0, +}; + /* * Configuration descriptor: same comments as above * regarding handling USB1 mode. @@ -200,17 +212,28 @@ static int ast_vhub_hub_dev_feature(struct ast_vhub_ep *ep, u16 wIndex, u16 wValue, bool is_set) { + u32 val; + EPDBG(ep, "%s_FEATURE(dev val=%02x)\n", is_set ? "SET" : "CLEAR", wValue); - if (wValue != USB_DEVICE_REMOTE_WAKEUP) - return std_req_stall; + if (wValue == USB_DEVICE_REMOTE_WAKEUP) { + ep->vhub->wakeup_en = is_set; + EPDBG(ep, "Hub remote wakeup %s\n", + is_set ? "enabled" : "disabled"); + return std_req_complete; + } - ep->vhub->wakeup_en = is_set; - EPDBG(ep, "Hub remote wakeup %s\n", - is_set ? "enabled" : "disabled"); + if (wValue == USB_DEVICE_TEST_MODE) { + val = readl(ep->vhub->regs + AST_VHUB_CTRL); + val &= ~GENMASK(10, 8); + val |= VHUB_CTRL_SET_TEST_MODE((wIndex >> 8) & 0x7); + writel(val, ep->vhub->regs + AST_VHUB_CTRL); - return std_req_complete; + return std_req_complete; + } + + return std_req_stall; } static int ast_vhub_hub_ep_feature(struct ast_vhub_ep *ep, @@ -271,9 +294,11 @@ static int ast_vhub_rep_desc(struct ast_vhub_ep *ep, BUILD_BUG_ON(dsize > sizeof(vhub->vhub_dev_desc)); BUILD_BUG_ON(USB_DT_DEVICE_SIZE >= AST_VHUB_EP0_MAX_PACKET); break; + case USB_DT_OTHER_SPEED_CONFIG: case USB_DT_CONFIG: dsize = AST_VHUB_CONF_DESC_SIZE; memcpy(ep->buf, &vhub->vhub_conf_desc, dsize); + ((u8 *)ep->buf)[1] = desc_type; BUILD_BUG_ON(dsize > sizeof(vhub->vhub_conf_desc)); BUILD_BUG_ON(AST_VHUB_CONF_DESC_SIZE >= AST_VHUB_EP0_MAX_PACKET); break; @@ -283,6 +308,10 @@ static int ast_vhub_rep_desc(struct ast_vhub_ep *ep, BUILD_BUG_ON(dsize > sizeof(vhub->vhub_hub_desc)); BUILD_BUG_ON(AST_VHUB_HUB_DESC_SIZE >= AST_VHUB_EP0_MAX_PACKET); break; + case USB_DT_DEVICE_QUALIFIER: + dsize = sizeof(vhub->vhub_qual_desc); + memcpy(ep->buf, &vhub->vhub_qual_desc, dsize); + break; default: return std_req_stall; } @@ -428,6 +457,8 @@ enum std_req_rc ast_vhub_std_hub_request(struct ast_vhub_ep *ep, switch (wValue >> 8) { case USB_DT_DEVICE: case USB_DT_CONFIG: + case USB_DT_DEVICE_QUALIFIER: + case USB_DT_OTHER_SPEED_CONFIG: return ast_vhub_rep_desc(ep, wValue >> 8, wLength); case USB_DT_STRING: @@ -1033,6 +1064,10 @@ static int ast_vhub_init_desc(struct ast_vhub *vhub) else ret = ast_vhub_str_alloc_add(vhub, &ast_vhub_strings); + /* Initialize vhub Qualifier Descriptor. */ + memcpy(&vhub->vhub_qual_desc, &ast_vhub_qual_desc, + sizeof(vhub->vhub_qual_desc)); + return ret; } diff --git a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h index 87a5dea12d3c..6b9dfa6e10eb 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +++ b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h @@ -425,6 +425,7 @@ struct ast_vhub { struct ast_vhub_full_cdesc vhub_conf_desc; struct usb_hub_descriptor vhub_hub_desc; struct list_head vhub_str_desc; + struct usb_qualifier_descriptor vhub_qual_desc; }; /* Standard request handlers result codes */ diff --git a/drivers/usb/gadget/udc/at91_udc.c b/drivers/usb/gadget/udc/at91_udc.c index d9ad9adf7348..dd0819df096e 100644 --- a/drivers/usb/gadget/udc/at91_udc.c +++ b/drivers/usb/gadget/udc/at91_udc.c @@ -25,7 +25,7 @@ #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> #include <linux/of.h> -#include <linux/of_gpio.h> +#include <linux/gpio/consumer.h> #include <linux/platform_data/atmel.h> #include <linux/regmap.h> #include <linux/mfd/syscon.h> @@ -1510,7 +1510,6 @@ static irqreturn_t at91_udc_irq (int irq, void *_udc) static void at91_vbus_update(struct at91_udc *udc, unsigned value) { - value ^= udc->board.vbus_active_low; if (value != udc->vbus) at91_vbus_session(&udc->gadget, value); } @@ -1521,7 +1520,7 @@ static irqreturn_t at91_vbus_irq(int irq, void *_udc) /* vbus needs at least brief debouncing */ udelay(10); - at91_vbus_update(udc, gpio_get_value(udc->board.vbus_pin)); + at91_vbus_update(udc, gpiod_get_value(udc->board.vbus_pin)); return IRQ_HANDLED; } @@ -1531,7 +1530,7 @@ static void at91_vbus_timer_work(struct work_struct *work) struct at91_udc *udc = container_of(work, struct at91_udc, vbus_timer_work); - at91_vbus_update(udc, gpio_get_value_cansleep(udc->board.vbus_pin)); + at91_vbus_update(udc, gpiod_get_value_cansleep(udc->board.vbus_pin)); if (!timer_pending(&udc->vbus_timer)) mod_timer(&udc->vbus_timer, jiffies + VBUS_POLL_TIMEOUT); @@ -1595,7 +1594,6 @@ static void at91udc_shutdown(struct platform_device *dev) static int at91rm9200_udc_init(struct at91_udc *udc) { struct at91_ep *ep; - int ret; int i; for (i = 0; i < NUM_ENDPOINTS; i++) { @@ -1615,32 +1613,23 @@ static int at91rm9200_udc_init(struct at91_udc *udc) } } - if (!gpio_is_valid(udc->board.pullup_pin)) { + if (!udc->board.pullup_pin) { DBG("no D+ pullup?\n"); return -ENODEV; } - ret = devm_gpio_request(&udc->pdev->dev, udc->board.pullup_pin, - "udc_pullup"); - if (ret) { - DBG("D+ pullup is busy\n"); - return ret; - } - - gpio_direction_output(udc->board.pullup_pin, - udc->board.pullup_active_low); + gpiod_direction_output(udc->board.pullup_pin, + gpiod_is_active_low(udc->board.pullup_pin)); return 0; } static void at91rm9200_udc_pullup(struct at91_udc *udc, int is_on) { - int active = !udc->board.pullup_active_low; - if (is_on) - gpio_set_value(udc->board.pullup_pin, active); + gpiod_set_value(udc->board.pullup_pin, 1); else - gpio_set_value(udc->board.pullup_pin, !active); + gpiod_set_value(udc->board.pullup_pin, 0); } static const struct at91_udc_caps at91rm9200_udc_caps = { @@ -1783,20 +1772,20 @@ static void at91udc_of_init(struct at91_udc *udc, struct device_node *np) { struct at91_udc_data *board = &udc->board; const struct of_device_id *match; - enum of_gpio_flags flags; u32 val; if (of_property_read_u32(np, "atmel,vbus-polled", &val) == 0) board->vbus_polled = 1; - board->vbus_pin = of_get_named_gpio_flags(np, "atmel,vbus-gpio", 0, - &flags); - board->vbus_active_low = (flags & OF_GPIO_ACTIVE_LOW) ? 1 : 0; + board->vbus_pin = gpiod_get_from_of_node(np, "atmel,vbus-gpio", 0, + GPIOD_IN, "udc_vbus"); + if (IS_ERR(board->vbus_pin)) + board->vbus_pin = NULL; - board->pullup_pin = of_get_named_gpio_flags(np, "atmel,pullup-gpio", 0, - &flags); - - board->pullup_active_low = (flags & OF_GPIO_ACTIVE_LOW) ? 1 : 0; + board->pullup_pin = gpiod_get_from_of_node(np, "atmel,pullup-gpio", 0, + GPIOD_ASIS, "udc_pullup"); + if (IS_ERR(board->pullup_pin)) + board->pullup_pin = NULL; match = of_match_node(at91_udc_dt_ids, np); if (match) @@ -1886,22 +1875,14 @@ static int at91udc_probe(struct platform_device *pdev) goto err_unprepare_iclk; } - if (gpio_is_valid(udc->board.vbus_pin)) { - retval = devm_gpio_request(dev, udc->board.vbus_pin, - "udc_vbus"); - if (retval) { - DBG("request vbus pin failed\n"); - goto err_unprepare_iclk; - } - - gpio_direction_input(udc->board.vbus_pin); + if (udc->board.vbus_pin) { + gpiod_direction_input(udc->board.vbus_pin); /* * Get the initial state of VBUS - we cannot expect * a pending interrupt. */ - udc->vbus = gpio_get_value_cansleep(udc->board.vbus_pin) ^ - udc->board.vbus_active_low; + udc->vbus = gpiod_get_value_cansleep(udc->board.vbus_pin); if (udc->board.vbus_polled) { INIT_WORK(&udc->vbus_timer_work, at91_vbus_timer_work); @@ -1910,7 +1891,7 @@ static int at91udc_probe(struct platform_device *pdev) jiffies + VBUS_POLL_TIMEOUT); } else { retval = devm_request_irq(dev, - gpio_to_irq(udc->board.vbus_pin), + gpiod_to_irq(udc->board.vbus_pin), at91_vbus_irq, 0, driver_name, udc); if (retval) { DBG("request vbus irq %d failed\n", @@ -1988,8 +1969,8 @@ static int at91udc_suspend(struct platform_device *pdev, pm_message_t mesg) enable_irq_wake(udc->udp_irq); udc->active_suspend = wake; - if (gpio_is_valid(udc->board.vbus_pin) && !udc->board.vbus_polled && wake) - enable_irq_wake(udc->board.vbus_pin); + if (udc->board.vbus_pin && !udc->board.vbus_polled && wake) + enable_irq_wake(gpiod_to_irq(udc->board.vbus_pin)); return 0; } @@ -1998,9 +1979,9 @@ static int at91udc_resume(struct platform_device *pdev) struct at91_udc *udc = platform_get_drvdata(pdev); unsigned long flags; - if (gpio_is_valid(udc->board.vbus_pin) && !udc->board.vbus_polled && + if (udc->board.vbus_pin && !udc->board.vbus_polled && udc->active_suspend) - disable_irq_wake(udc->board.vbus_pin); + disable_irq_wake(gpiod_to_irq(udc->board.vbus_pin)); /* maybe reconnect to host; if so, clocks on */ if (udc->active_suspend) diff --git a/drivers/usb/gadget/udc/at91_udc.h b/drivers/usb/gadget/udc/at91_udc.h index fd58c5b81826..28c1042f8623 100644 --- a/drivers/usb/gadget/udc/at91_udc.h +++ b/drivers/usb/gadget/udc/at91_udc.h @@ -109,11 +109,9 @@ struct at91_udc_caps { }; struct at91_udc_data { - int vbus_pin; /* high == host powering us */ - u8 vbus_active_low; /* vbus polarity */ - u8 vbus_polled; /* Use polling, not interrupt */ - int pullup_pin; /* active == D+ pulled up */ - u8 pullup_active_low; /* true == pullup_pin is active low */ + struct gpio_desc *vbus_pin; /* high == host powering us */ + u8 vbus_polled; /* Use polling, not interrupt */ + struct gpio_desc *pullup_pin; /* active == D+ pulled up */ }; /* diff --git a/drivers/usb/gadget/udc/bcm63xx_udc.c b/drivers/usb/gadget/udc/bcm63xx_udc.c index a9f07c59fc37..2cdb07905bde 100644 --- a/drivers/usb/gadget/udc/bcm63xx_udc.c +++ b/drivers/usb/gadget/udc/bcm63xx_udc.c @@ -2321,8 +2321,10 @@ static int bcm63xx_udc_probe(struct platform_device *pdev) /* IRQ resource #0: control interrupt (VBUS, speed, etc.) */ irq = platform_get_irq(pdev, 0); - if (irq < 0) + if (irq < 0) { + rc = irq; goto out_uninit; + } if (devm_request_irq(dev, irq, &bcm63xx_udc_ctrl_isr, 0, dev_name(dev), udc) < 0) goto report_request_failure; @@ -2330,8 +2332,10 @@ static int bcm63xx_udc_probe(struct platform_device *pdev) /* IRQ resources #1-6: data interrupts for IUDMA channels 0-5 */ for (i = 0; i < BCM63XX_NUM_IUDMA; i++) { irq = platform_get_irq(pdev, i + 1); - if (irq < 0) + if (irq < 0) { + rc = irq; goto out_uninit; + } if (devm_request_irq(dev, irq, &bcm63xx_udc_data_isr, 0, dev_name(dev), &udc->iudma[i]) < 0) goto report_request_failure; diff --git a/drivers/usb/gadget/udc/bdc/bdc_core.c b/drivers/usb/gadget/udc/bdc/bdc_core.c index fa1a3908ec3b..9849e0c86e23 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_core.c +++ b/drivers/usb/gadget/udc/bdc/bdc_core.c @@ -623,6 +623,7 @@ static int bdc_resume(struct device *dev) ret = bdc_reinit(bdc); if (ret) { dev_err(bdc->dev, "err in bdc reinit\n"); + clk_disable_unprepare(bdc->clk); return ret; } diff --git a/drivers/usb/gadget/udc/mv_udc_core.c b/drivers/usb/gadget/udc/mv_udc_core.c index 7f24ce400b59..b6d34dda028b 100644 --- a/drivers/usb/gadget/udc/mv_udc_core.c +++ b/drivers/usb/gadget/udc/mv_udc_core.c @@ -2084,10 +2084,8 @@ static int mv_udc_remove(struct platform_device *pdev) usb_del_gadget_udc(&udc->gadget); - if (udc->qwork) { - flush_workqueue(udc->qwork); + if (udc->qwork) destroy_workqueue(udc->qwork); - } /* free memory allocated in probe */ dma_pool_destroy(udc->dtd_pool); diff --git a/drivers/usb/gadget/udc/pxa25x_udc.c b/drivers/usb/gadget/udc/pxa25x_udc.c index 52cdfd8212d6..b38747fd3bb0 100644 --- a/drivers/usb/gadget/udc/pxa25x_udc.c +++ b/drivers/usb/gadget/udc/pxa25x_udc.c @@ -2364,7 +2364,7 @@ static int pxa25x_udc_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 0); if (irq < 0) - return -ENODEV; + return irq; dev->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(dev->regs)) diff --git a/drivers/usb/gadget/udc/udc-xilinx.c b/drivers/usb/gadget/udc/udc-xilinx.c index 857159dd5ae0..6ce886fb7bfe 100644 --- a/drivers/usb/gadget/udc/udc-xilinx.c +++ b/drivers/usb/gadget/udc/udc-xilinx.c @@ -2179,6 +2179,61 @@ static int xudc_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_SLEEP +static int xudc_suspend(struct device *dev) +{ + struct xusb_udc *udc; + u32 crtlreg; + unsigned long flags; + + udc = dev_get_drvdata(dev); + + spin_lock_irqsave(&udc->lock, flags); + + crtlreg = udc->read_fn(udc->addr + XUSB_CONTROL_OFFSET); + crtlreg &= ~XUSB_CONTROL_USB_READY_MASK; + + udc->write_fn(udc->addr, XUSB_CONTROL_OFFSET, crtlreg); + + spin_unlock_irqrestore(&udc->lock, flags); + if (udc->driver && udc->driver->suspend) + udc->driver->suspend(&udc->gadget); + + clk_disable(udc->clk); + + return 0; +} + +static int xudc_resume(struct device *dev) +{ + struct xusb_udc *udc; + u32 crtlreg; + unsigned long flags; + int ret; + + udc = dev_get_drvdata(dev); + + ret = clk_enable(udc->clk); + if (ret < 0) + return ret; + + spin_lock_irqsave(&udc->lock, flags); + + crtlreg = udc->read_fn(udc->addr + XUSB_CONTROL_OFFSET); + crtlreg |= XUSB_CONTROL_USB_READY_MASK; + + udc->write_fn(udc->addr, XUSB_CONTROL_OFFSET, crtlreg); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops xudc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(xudc_suspend, xudc_resume) +}; + /* Match table for of_platform binding */ static const struct of_device_id usb_of_match[] = { { .compatible = "xlnx,usb2-device-4.00.a", }, @@ -2190,6 +2245,7 @@ static struct platform_driver xudc_driver = { .driver = { .name = driver_name, .of_match_table = usb_of_match, + .pm = &xudc_pm_ops, }, .probe = xudc_probe, .remove = xudc_remove, diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index d1d926f8f9c2..57ca5f97a3dc 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -772,3 +772,14 @@ config USB_HCD_TEST_MODE This option is of interest only to developers who need to validate their USB hardware designs. It is not needed for normal use. If unsure, say N. + +config USB_XEN_HCD + tristate "Xen usb virtual host driver" + depends on XEN + select XEN_XENBUS_FRONTEND + help + The Xen usb virtual host driver serves as a frontend driver enabling + a Xen guest system to access USB Devices passed through to the guest + by the Xen host (usually Dom0). + Only needed if the kernel is running in a Xen guest and generic + access to a USB device is needed. diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 171de4df50bd..2948983618fb 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -85,3 +85,4 @@ obj-$(CONFIG_USB_HCD_BCMA) += bcma-hcd.o obj-$(CONFIG_USB_HCD_SSB) += ssb-hcd.o obj-$(CONFIG_USB_FOTG210_HCD) += fotg210-hcd.o obj-$(CONFIG_USB_MAX3421_HCD) += max3421-hcd.o +obj-$(CONFIG_USB_XEN_HCD) += xen-hcd.o diff --git a/drivers/usb/host/ehci-brcm.c b/drivers/usb/host/ehci-brcm.c index d3626bfa966b..6a0f64c9e5e8 100644 --- a/drivers/usb/host/ehci-brcm.c +++ b/drivers/usb/host/ehci-brcm.c @@ -62,8 +62,12 @@ static int ehci_brcm_hub_control( u32 __iomem *status_reg; unsigned long flags; int retval, irq_disabled = 0; + u32 temp; - status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; + temp = (wIndex & 0xff) - 1; + if (temp >= HCS_N_PORTS_MAX) /* Avoid index-out-of-bounds warning */ + temp = 0; + status_reg = &ehci->regs->port_status[temp]; /* * RESUME is cleared when GetPortStatus() is called 20ms after start diff --git a/drivers/usb/host/ehci-sh.c b/drivers/usb/host/ehci-sh.c index c25c51d26f26..882231b5c382 100644 --- a/drivers/usb/host/ehci-sh.c +++ b/drivers/usb/host/ehci-sh.c @@ -82,8 +82,8 @@ static int ehci_hcd_sh_probe(struct platform_device *pdev) return -ENODEV; irq = platform_get_irq(pdev, 0); - if (irq <= 0) { - ret = -ENODEV; + if (irq < 0) { + ret = irq; goto fail_create_hcd; } diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c index ded9738392e4..45dcf8292072 100644 --- a/drivers/usb/host/ohci-omap.c +++ b/drivers/usb/host/ohci-omap.c @@ -306,7 +306,7 @@ static int ohci_hcd_omap_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 0); if (irq < 0) { - retval = -ENXIO; + retval = irq; goto err3; } retval = usb_add_hcd(hcd, irq, 0); diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c index 1bec9b585e2d..12264c048601 100644 --- a/drivers/usb/host/ohci-s3c2410.c +++ b/drivers/usb/host/ohci-s3c2410.c @@ -356,7 +356,7 @@ static int ohci_hcd_s3c2410_probe(struct platform_device *dev) { struct usb_hcd *hcd = NULL; struct s3c2410_hcd_info *info = dev_get_platdata(&dev->dev); - int retval; + int retval, irq; s3c2410_usb_set_power(info, 1, 1); s3c2410_usb_set_power(info, 2, 1); @@ -388,9 +388,15 @@ static int ohci_hcd_s3c2410_probe(struct platform_device *dev) goto err_put; } + irq = platform_get_irq(dev, 0); + if (irq < 0) { + retval = irq; + goto err_put; + } + s3c2410_start_hc(dev, hcd); - retval = usb_add_hcd(hcd, dev->resource[1].start, 0); + retval = usb_add_hcd(hcd, irq, 0); if (retval != 0) goto err_ioremap; diff --git a/drivers/usb/host/ohci-spear.c b/drivers/usb/host/ohci-spear.c index b4cd9e6c72fd..9b81f420656d 100644 --- a/drivers/usb/host/ohci-spear.c +++ b/drivers/usb/host/ohci-spear.c @@ -76,7 +76,7 @@ static int spear_ohci_hcd_drv_probe(struct platform_device *pdev) goto err_put_hcd; } - hcd->rsrc_start = pdev->resource[0].start; + hcd->rsrc_start = res->start; hcd->rsrc_len = resource_size(res); sohci_p = to_spear_ohci(hcd); diff --git a/drivers/usb/host/u132-hcd.c b/drivers/usb/host/u132-hcd.c index ae882d76612b..d879d6af5710 100644 --- a/drivers/usb/host/u132-hcd.c +++ b/drivers/usb/host/u132-hcd.c @@ -3211,7 +3211,6 @@ static void __exit u132_hcd_exit(void) platform_driver_unregister(&u132_platform_driver); printk(KERN_INFO "u132-hcd driver deregistered\n"); wait_event(u132_hcd_wait, u132_instances == 0); - flush_workqueue(workqueue); destroy_workqueue(workqueue); } diff --git a/drivers/usb/host/uhci-platform.c b/drivers/usb/host/uhci-platform.c index 70dbd95c3f06..b2049b47a08d 100644 --- a/drivers/usb/host/uhci-platform.c +++ b/drivers/usb/host/uhci-platform.c @@ -113,7 +113,8 @@ static int uhci_hcd_platform_probe(struct platform_device *pdev) num_ports); } if (of_device_is_compatible(np, "aspeed,ast2400-uhci") || - of_device_is_compatible(np, "aspeed,ast2500-uhci")) { + of_device_is_compatible(np, "aspeed,ast2500-uhci") || + of_device_is_compatible(np, "aspeed,ast2600-uhci")) { uhci->is_aspeed = 1; dev_info(&pdev->dev, "Enabled Aspeed implementation workarounds\n"); @@ -132,7 +133,11 @@ static int uhci_hcd_platform_probe(struct platform_device *pdev) goto err_rmr; } - ret = usb_add_hcd(hcd, pdev->resource[1].start, IRQF_SHARED); + ret = platform_get_irq(pdev, 0); + if (ret < 0) + goto err_clk; + + ret = usb_add_hcd(hcd, ret, IRQF_SHARED); if (ret) goto err_clk; diff --git a/drivers/usb/host/xen-hcd.c b/drivers/usb/host/xen-hcd.c new file mode 100644 index 000000000000..be09fd9bac58 --- /dev/null +++ b/drivers/usb/host/xen-hcd.c @@ -0,0 +1,1609 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * xen-hcd.c + * + * Xen USB Virtual Host Controller driver + * + * Copyright (C) 2009, FUJITSU LABORATORIES LTD. + * Author: Noboru Iwamatsu <n_iwamatsu@jp.fujitsu.com> + */ + +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/list.h> +#include <linux/usb/hcd.h> +#include <linux/io.h> + +#include <xen/xen.h> +#include <xen/xenbus.h> +#include <xen/grant_table.h> +#include <xen/events.h> +#include <xen/page.h> + +#include <xen/interface/io/usbif.h> + +/* Private per-URB data */ +struct urb_priv { + struct list_head list; + struct urb *urb; + int req_id; /* RING_REQUEST id for submitting */ + int unlink_req_id; /* RING_REQUEST id for unlinking */ + int status; + bool unlinked; /* dequeued marker */ +}; + +/* virtual roothub port status */ +struct rhport_status { + __u32 status; + bool resuming; /* in resuming */ + bool c_connection; /* connection changed */ + unsigned long timeout; +}; + +/* status of attached device */ +struct vdevice_status { + int devnum; + enum usb_device_state status; + enum usb_device_speed speed; +}; + +/* RING request shadow */ +struct usb_shadow { + struct xenusb_urb_request req; + struct urb *urb; +}; + +struct xenhcd_info { + /* Virtual Host Controller has 4 urb queues */ + struct list_head pending_submit_list; + struct list_head pending_unlink_list; + struct list_head in_progress_list; + struct list_head giveback_waiting_list; + + spinlock_t lock; + + /* timer that kick pending and giveback waiting urbs */ + struct timer_list watchdog; + unsigned long actions; + + /* virtual root hub */ + int rh_numports; + struct rhport_status ports[XENUSB_MAX_PORTNR]; + struct vdevice_status devices[XENUSB_MAX_PORTNR]; + + /* Xen related staff */ + struct xenbus_device *xbdev; + int urb_ring_ref; + int conn_ring_ref; + struct xenusb_urb_front_ring urb_ring; + struct xenusb_conn_front_ring conn_ring; + + unsigned int evtchn; + unsigned int irq; + struct usb_shadow shadow[XENUSB_URB_RING_SIZE]; + unsigned int shadow_free; + + bool error; +}; + +#define GRANT_INVALID_REF 0 + +#define XENHCD_RING_JIFFIES (HZ/200) +#define XENHCD_SCAN_JIFFIES 1 + +enum xenhcd_timer_action { + TIMER_RING_WATCHDOG, + TIMER_SCAN_PENDING_URBS, +}; + +static struct kmem_cache *xenhcd_urbp_cachep; + +static inline struct xenhcd_info *xenhcd_hcd_to_info(struct usb_hcd *hcd) +{ + return (struct xenhcd_info *)hcd->hcd_priv; +} + +static inline struct usb_hcd *xenhcd_info_to_hcd(struct xenhcd_info *info) +{ + return container_of((void *)info, struct usb_hcd, hcd_priv); +} + +static void xenhcd_set_error(struct xenhcd_info *info, const char *msg) +{ + info->error = true; + + pr_alert("xen-hcd: protocol error: %s!\n", msg); +} + +static inline void xenhcd_timer_action_done(struct xenhcd_info *info, + enum xenhcd_timer_action action) +{ + clear_bit(action, &info->actions); +} + +static void xenhcd_timer_action(struct xenhcd_info *info, + enum xenhcd_timer_action action) +{ + if (timer_pending(&info->watchdog) && + test_bit(TIMER_SCAN_PENDING_URBS, &info->actions)) + return; + + if (!test_and_set_bit(action, &info->actions)) { + unsigned long t; + + switch (action) { + case TIMER_RING_WATCHDOG: + t = XENHCD_RING_JIFFIES; + break; + default: + t = XENHCD_SCAN_JIFFIES; + break; + } + mod_timer(&info->watchdog, t + jiffies); + } +} + +/* + * set virtual port connection status + */ +static void xenhcd_set_connect_state(struct xenhcd_info *info, int portnum) +{ + int port; + + port = portnum - 1; + if (info->ports[port].status & USB_PORT_STAT_POWER) { + switch (info->devices[port].speed) { + case XENUSB_SPEED_NONE: + info->ports[port].status &= + ~(USB_PORT_STAT_CONNECTION | + USB_PORT_STAT_ENABLE | + USB_PORT_STAT_LOW_SPEED | + USB_PORT_STAT_HIGH_SPEED | + USB_PORT_STAT_SUSPEND); + break; + case XENUSB_SPEED_LOW: + info->ports[port].status |= USB_PORT_STAT_CONNECTION; + info->ports[port].status |= USB_PORT_STAT_LOW_SPEED; + break; + case XENUSB_SPEED_FULL: + info->ports[port].status |= USB_PORT_STAT_CONNECTION; + break; + case XENUSB_SPEED_HIGH: + info->ports[port].status |= USB_PORT_STAT_CONNECTION; + info->ports[port].status |= USB_PORT_STAT_HIGH_SPEED; + break; + default: /* error */ + return; + } + info->ports[port].status |= (USB_PORT_STAT_C_CONNECTION << 16); + } +} + +/* + * set virtual device connection status + */ +static int xenhcd_rhport_connect(struct xenhcd_info *info, __u8 portnum, + __u8 speed) +{ + int port; + + if (portnum < 1 || portnum > info->rh_numports) + return -EINVAL; /* invalid port number */ + + port = portnum - 1; + if (info->devices[port].speed != speed) { + switch (speed) { + case XENUSB_SPEED_NONE: /* disconnect */ + info->devices[port].status = USB_STATE_NOTATTACHED; + break; + case XENUSB_SPEED_LOW: + case XENUSB_SPEED_FULL: + case XENUSB_SPEED_HIGH: + info->devices[port].status = USB_STATE_ATTACHED; + break; + default: /* error */ + return -EINVAL; + } + info->devices[port].speed = speed; + info->ports[port].c_connection = true; + + xenhcd_set_connect_state(info, portnum); + } + + return 0; +} + +/* + * SetPortFeature(PORT_SUSPENDED) + */ +static void xenhcd_rhport_suspend(struct xenhcd_info *info, int portnum) +{ + int port; + + port = portnum - 1; + info->ports[port].status |= USB_PORT_STAT_SUSPEND; + info->devices[port].status = USB_STATE_SUSPENDED; +} + +/* + * ClearPortFeature(PORT_SUSPENDED) + */ +static void xenhcd_rhport_resume(struct xenhcd_info *info, int portnum) +{ + int port; + + port = portnum - 1; + if (info->ports[port].status & USB_PORT_STAT_SUSPEND) { + info->ports[port].resuming = true; + info->ports[port].timeout = jiffies + msecs_to_jiffies(20); + } +} + +/* + * SetPortFeature(PORT_POWER) + */ +static void xenhcd_rhport_power_on(struct xenhcd_info *info, int portnum) +{ + int port; + + port = portnum - 1; + if ((info->ports[port].status & USB_PORT_STAT_POWER) == 0) { + info->ports[port].status |= USB_PORT_STAT_POWER; + if (info->devices[port].status != USB_STATE_NOTATTACHED) + info->devices[port].status = USB_STATE_POWERED; + if (info->ports[port].c_connection) + xenhcd_set_connect_state(info, portnum); + } +} + +/* + * ClearPortFeature(PORT_POWER) + * SetConfiguration(non-zero) + * Power_Source_Off + * Over-current + */ +static void xenhcd_rhport_power_off(struct xenhcd_info *info, int portnum) +{ + int port; + + port = portnum - 1; + if (info->ports[port].status & USB_PORT_STAT_POWER) { + info->ports[port].status = 0; + if (info->devices[port].status != USB_STATE_NOTATTACHED) + info->devices[port].status = USB_STATE_ATTACHED; + } +} + +/* + * ClearPortFeature(PORT_ENABLE) + */ +static void xenhcd_rhport_disable(struct xenhcd_info *info, int portnum) +{ + int port; + + port = portnum - 1; + info->ports[port].status &= ~USB_PORT_STAT_ENABLE; + info->ports[port].status &= ~USB_PORT_STAT_SUSPEND; + info->ports[port].resuming = false; + if (info->devices[port].status != USB_STATE_NOTATTACHED) + info->devices[port].status = USB_STATE_POWERED; +} + +/* + * SetPortFeature(PORT_RESET) + */ +static void xenhcd_rhport_reset(struct xenhcd_info *info, int portnum) +{ + int port; + + port = portnum - 1; + info->ports[port].status &= ~(USB_PORT_STAT_ENABLE | + USB_PORT_STAT_LOW_SPEED | + USB_PORT_STAT_HIGH_SPEED); + info->ports[port].status |= USB_PORT_STAT_RESET; + + if (info->devices[port].status != USB_STATE_NOTATTACHED) + info->devices[port].status = USB_STATE_ATTACHED; + + /* 10msec reset signaling */ + info->ports[port].timeout = jiffies + msecs_to_jiffies(10); +} + +#ifdef CONFIG_PM +static int xenhcd_bus_suspend(struct usb_hcd *hcd) +{ + struct xenhcd_info *info = xenhcd_hcd_to_info(hcd); + int ret = 0; + int i, ports; + + ports = info->rh_numports; + + spin_lock_irq(&info->lock); + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + ret = -ESHUTDOWN; + } else { + /* suspend any active ports*/ + for (i = 1; i <= ports; i++) + xenhcd_rhport_suspend(info, i); + } + spin_unlock_irq(&info->lock); + + del_timer_sync(&info->watchdog); + + return ret; +} + +static int xenhcd_bus_resume(struct usb_hcd *hcd) +{ + struct xenhcd_info *info = xenhcd_hcd_to_info(hcd); + int ret = 0; + int i, ports; + + ports = info->rh_numports; + + spin_lock_irq(&info->lock); + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + ret = -ESHUTDOWN; + } else { + /* resume any suspended ports*/ + for (i = 1; i <= ports; i++) + xenhcd_rhport_resume(info, i); + } + spin_unlock_irq(&info->lock); + + return ret; +} +#endif + +static void xenhcd_hub_descriptor(struct xenhcd_info *info, + struct usb_hub_descriptor *desc) +{ + __u16 temp; + int ports = info->rh_numports; + + desc->bDescriptorType = 0x29; + desc->bPwrOn2PwrGood = 10; /* EHCI says 20ms max */ + desc->bHubContrCurrent = 0; + desc->bNbrPorts = ports; + + /* size of DeviceRemovable and PortPwrCtrlMask fields */ + temp = 1 + (ports / 8); + desc->bDescLength = 7 + 2 * temp; + + /* bitmaps for DeviceRemovable and PortPwrCtrlMask */ + memset(&desc->u.hs.DeviceRemovable[0], 0, temp); + memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); + + /* per-port over current reporting and no power switching */ + temp = 0x000a; + desc->wHubCharacteristics = cpu_to_le16(temp); +} + +/* port status change mask for hub_status_data */ +#define PORT_C_MASK ((USB_PORT_STAT_C_CONNECTION | \ + USB_PORT_STAT_C_ENABLE | \ + USB_PORT_STAT_C_SUSPEND | \ + USB_PORT_STAT_C_OVERCURRENT | \ + USB_PORT_STAT_C_RESET) << 16) + +/* + * See USB 2.0 Spec, 11.12.4 Hub and Port Status Change Bitmap. + * If port status changed, writes the bitmap to buf and return + * that length(number of bytes). + * If Nothing changed, return 0. + */ +static int xenhcd_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct xenhcd_info *info = xenhcd_hcd_to_info(hcd); + int ports; + int i; + unsigned long flags; + int ret; + int changed = 0; + + /* initialize the status to no-changes */ + ports = info->rh_numports; + ret = 1 + (ports / 8); + memset(buf, 0, ret); + + spin_lock_irqsave(&info->lock, flags); + + for (i = 0; i < ports; i++) { + /* check status for each port */ + if (info->ports[i].status & PORT_C_MASK) { + buf[(i + 1) / 8] |= 1 << (i + 1) % 8; + changed = 1; + } + } + + if ((hcd->state == HC_STATE_SUSPENDED) && (changed == 1)) + usb_hcd_resume_root_hub(hcd); + + spin_unlock_irqrestore(&info->lock, flags); + + return changed ? ret : 0; +} + +static int xenhcd_hub_control(struct usb_hcd *hcd, __u16 typeReq, __u16 wValue, + __u16 wIndex, char *buf, __u16 wLength) +{ + struct xenhcd_info *info = xenhcd_hcd_to_info(hcd); + int ports = info->rh_numports; + unsigned long flags; + int ret = 0; + int i; + int changed = 0; + + spin_lock_irqsave(&info->lock, flags); + switch (typeReq) { + case ClearHubFeature: + /* ignore this request */ + break; + case ClearPortFeature: + if (!wIndex || wIndex > ports) + goto error; + + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + xenhcd_rhport_resume(info, wIndex); + break; + case USB_PORT_FEAT_POWER: + xenhcd_rhport_power_off(info, wIndex); + break; + case USB_PORT_FEAT_ENABLE: + xenhcd_rhport_disable(info, wIndex); + break; + case USB_PORT_FEAT_C_CONNECTION: + info->ports[wIndex - 1].c_connection = false; + fallthrough; + default: + info->ports[wIndex - 1].status &= ~(1 << wValue); + break; + } + break; + case GetHubDescriptor: + xenhcd_hub_descriptor(info, (struct usb_hub_descriptor *)buf); + break; + case GetHubStatus: + /* always local power supply good and no over-current exists. */ + *(__le32 *)buf = cpu_to_le32(0); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + + wIndex--; + + /* resume completion */ + if (info->ports[wIndex].resuming && + time_after_eq(jiffies, info->ports[wIndex].timeout)) { + info->ports[wIndex].status |= + USB_PORT_STAT_C_SUSPEND << 16; + info->ports[wIndex].status &= ~USB_PORT_STAT_SUSPEND; + } + + /* reset completion */ + if ((info->ports[wIndex].status & USB_PORT_STAT_RESET) != 0 && + time_after_eq(jiffies, info->ports[wIndex].timeout)) { + info->ports[wIndex].status |= + USB_PORT_STAT_C_RESET << 16; + info->ports[wIndex].status &= ~USB_PORT_STAT_RESET; + + if (info->devices[wIndex].status != + USB_STATE_NOTATTACHED) { + info->ports[wIndex].status |= + USB_PORT_STAT_ENABLE; + info->devices[wIndex].status = + USB_STATE_DEFAULT; + } + + switch (info->devices[wIndex].speed) { + case XENUSB_SPEED_LOW: + info->ports[wIndex].status |= + USB_PORT_STAT_LOW_SPEED; + break; + case XENUSB_SPEED_HIGH: + info->ports[wIndex].status |= + USB_PORT_STAT_HIGH_SPEED; + break; + default: + break; + } + } + + *(__le32 *)buf = cpu_to_le32(info->ports[wIndex].status); + break; + case SetPortFeature: + if (!wIndex || wIndex > ports) + goto error; + + switch (wValue) { + case USB_PORT_FEAT_POWER: + xenhcd_rhport_power_on(info, wIndex); + break; + case USB_PORT_FEAT_RESET: + xenhcd_rhport_reset(info, wIndex); + break; + case USB_PORT_FEAT_SUSPEND: + xenhcd_rhport_suspend(info, wIndex); + break; + default: + if (info->ports[wIndex-1].status & USB_PORT_STAT_POWER) + info->ports[wIndex-1].status |= (1 << wValue); + } + break; + + case SetHubFeature: + /* not supported */ + default: +error: + ret = -EPIPE; + } + spin_unlock_irqrestore(&info->lock, flags); + + /* check status for each port */ + for (i = 0; i < ports; i++) { + if (info->ports[i].status & PORT_C_MASK) + changed = 1; + } + if (changed) + usb_hcd_poll_rh_status(hcd); + + return ret; +} + +static void xenhcd_free_urb_priv(struct urb_priv *urbp) +{ + urbp->urb->hcpriv = NULL; + kmem_cache_free(xenhcd_urbp_cachep, urbp); +} + +static inline unsigned int xenhcd_get_id_from_freelist(struct xenhcd_info *info) +{ + unsigned int free; + + free = info->shadow_free; + info->shadow_free = info->shadow[free].req.id; + info->shadow[free].req.id = 0x0fff; /* debug */ + return free; +} + +static inline void xenhcd_add_id_to_freelist(struct xenhcd_info *info, + unsigned int id) +{ + info->shadow[id].req.id = info->shadow_free; + info->shadow[id].urb = NULL; + info->shadow_free = id; +} + +static inline int xenhcd_count_pages(void *addr, int length) +{ + unsigned long vaddr = (unsigned long)addr; + + return PFN_UP(vaddr + length) - PFN_DOWN(vaddr); +} + +static void xenhcd_gnttab_map(struct xenhcd_info *info, void *addr, int length, + grant_ref_t *gref_head, + struct xenusb_request_segment *seg, + int nr_pages, int flags) +{ + grant_ref_t ref; + unsigned long buffer_mfn; + unsigned int offset; + unsigned int len = length; + unsigned int bytes; + int i; + + for (i = 0; i < nr_pages; i++) { + buffer_mfn = PFN_DOWN(arbitrary_virt_to_machine(addr).maddr); + offset = offset_in_page(addr); + + bytes = PAGE_SIZE - offset; + if (bytes > len) + bytes = len; + + ref = gnttab_claim_grant_reference(gref_head); + gnttab_grant_foreign_access_ref(ref, info->xbdev->otherend_id, + buffer_mfn, flags); + seg[i].gref = ref; + seg[i].offset = (__u16)offset; + seg[i].length = (__u16)bytes; + + addr += bytes; + len -= bytes; + } +} + +static __u32 xenhcd_pipe_urb_to_xenusb(__u32 urb_pipe, __u8 port) +{ + static __u32 pipe; + + pipe = usb_pipedevice(urb_pipe) << XENUSB_PIPE_DEV_SHIFT; + pipe |= usb_pipeendpoint(urb_pipe) << XENUSB_PIPE_EP_SHIFT; + if (usb_pipein(urb_pipe)) + pipe |= XENUSB_PIPE_DIR; + switch (usb_pipetype(urb_pipe)) { + case PIPE_ISOCHRONOUS: + pipe |= XENUSB_PIPE_TYPE_ISOC << XENUSB_PIPE_TYPE_SHIFT; + break; + case PIPE_INTERRUPT: + pipe |= XENUSB_PIPE_TYPE_INT << XENUSB_PIPE_TYPE_SHIFT; + break; + case PIPE_CONTROL: + pipe |= XENUSB_PIPE_TYPE_CTRL << XENUSB_PIPE_TYPE_SHIFT; + break; + case PIPE_BULK: + pipe |= XENUSB_PIPE_TYPE_BULK << XENUSB_PIPE_TYPE_SHIFT; + break; + } + pipe = xenusb_setportnum_pipe(pipe, port); + + return pipe; +} + +static int xenhcd_map_urb_for_request(struct xenhcd_info *info, struct urb *urb, + struct xenusb_urb_request *req) +{ + grant_ref_t gref_head; + int nr_buff_pages = 0; + int nr_isodesc_pages = 0; + int nr_grants = 0; + + if (urb->transfer_buffer_length) { + nr_buff_pages = xenhcd_count_pages(urb->transfer_buffer, + urb->transfer_buffer_length); + + if (usb_pipeisoc(urb->pipe)) + nr_isodesc_pages = xenhcd_count_pages( + &urb->iso_frame_desc[0], + sizeof(struct usb_iso_packet_descriptor) * + urb->number_of_packets); + + nr_grants = nr_buff_pages + nr_isodesc_pages; + if (nr_grants > XENUSB_MAX_SEGMENTS_PER_REQUEST) { + pr_err("xenhcd: error: %d grants\n", nr_grants); + return -E2BIG; + } + + if (gnttab_alloc_grant_references(nr_grants, &gref_head)) { + pr_err("xenhcd: gnttab_alloc_grant_references() error\n"); + return -ENOMEM; + } + + xenhcd_gnttab_map(info, urb->transfer_buffer, + urb->transfer_buffer_length, &gref_head, + &req->seg[0], nr_buff_pages, + usb_pipein(urb->pipe) ? 0 : GTF_readonly); + } + + req->pipe = xenhcd_pipe_urb_to_xenusb(urb->pipe, urb->dev->portnum); + req->transfer_flags = 0; + if (urb->transfer_flags & URB_SHORT_NOT_OK) + req->transfer_flags |= XENUSB_SHORT_NOT_OK; + req->buffer_length = urb->transfer_buffer_length; + req->nr_buffer_segs = nr_buff_pages; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_ISOCHRONOUS: + req->u.isoc.interval = urb->interval; + req->u.isoc.start_frame = urb->start_frame; + req->u.isoc.number_of_packets = urb->number_of_packets; + req->u.isoc.nr_frame_desc_segs = nr_isodesc_pages; + + xenhcd_gnttab_map(info, &urb->iso_frame_desc[0], + sizeof(struct usb_iso_packet_descriptor) * + urb->number_of_packets, + &gref_head, &req->seg[nr_buff_pages], + nr_isodesc_pages, 0); + break; + case PIPE_INTERRUPT: + req->u.intr.interval = urb->interval; + break; + case PIPE_CONTROL: + if (urb->setup_packet) + memcpy(req->u.ctrl, urb->setup_packet, 8); + break; + case PIPE_BULK: + break; + default: + break; + } + + if (nr_grants) + gnttab_free_grant_references(gref_head); + + return 0; +} + +static void xenhcd_gnttab_done(struct usb_shadow *shadow) +{ + int nr_segs = 0; + int i; + + nr_segs = shadow->req.nr_buffer_segs; + + if (xenusb_pipeisoc(shadow->req.pipe)) + nr_segs += shadow->req.u.isoc.nr_frame_desc_segs; + + for (i = 0; i < nr_segs; i++) + gnttab_end_foreign_access(shadow->req.seg[i].gref, 0, 0UL); + + shadow->req.nr_buffer_segs = 0; + shadow->req.u.isoc.nr_frame_desc_segs = 0; +} + +static int xenhcd_translate_status(int status) +{ + switch (status) { + case XENUSB_STATUS_OK: + return 0; + case XENUSB_STATUS_NODEV: + return -ENODEV; + case XENUSB_STATUS_INVAL: + return -EINVAL; + case XENUSB_STATUS_STALL: + return -EPIPE; + case XENUSB_STATUS_IOERROR: + return -EPROTO; + case XENUSB_STATUS_BABBLE: + return -EOVERFLOW; + default: + return -ESHUTDOWN; + } +} + +static void xenhcd_giveback_urb(struct xenhcd_info *info, struct urb *urb, + int status) +{ + struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; + int priv_status = urbp->status; + + list_del_init(&urbp->list); + xenhcd_free_urb_priv(urbp); + + if (urb->status == -EINPROGRESS) + urb->status = xenhcd_translate_status(status); + + spin_unlock(&info->lock); + usb_hcd_giveback_urb(xenhcd_info_to_hcd(info), urb, + priv_status <= 0 ? priv_status : urb->status); + spin_lock(&info->lock); +} + +static int xenhcd_do_request(struct xenhcd_info *info, struct urb_priv *urbp) +{ + struct xenusb_urb_request *req; + struct urb *urb = urbp->urb; + unsigned int id; + int notify; + int ret; + + id = xenhcd_get_id_from_freelist(info); + req = &info->shadow[id].req; + req->id = id; + + if (unlikely(urbp->unlinked)) { + req->u.unlink.unlink_id = urbp->req_id; + req->pipe = xenusb_setunlink_pipe(xenhcd_pipe_urb_to_xenusb( + urb->pipe, urb->dev->portnum)); + urbp->unlink_req_id = id; + } else { + ret = xenhcd_map_urb_for_request(info, urb, req); + if (ret) { + xenhcd_add_id_to_freelist(info, id); + return ret; + } + urbp->req_id = id; + } + + req = RING_GET_REQUEST(&info->urb_ring, info->urb_ring.req_prod_pvt); + *req = info->shadow[id].req; + + info->urb_ring.req_prod_pvt++; + info->shadow[id].urb = urb; + + RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&info->urb_ring, notify); + if (notify) + notify_remote_via_irq(info->irq); + + return 0; +} + +static void xenhcd_kick_pending_urbs(struct xenhcd_info *info) +{ + struct urb_priv *urbp; + + while (!list_empty(&info->pending_submit_list)) { + if (RING_FULL(&info->urb_ring)) { + xenhcd_timer_action(info, TIMER_RING_WATCHDOG); + return; + } + + urbp = list_entry(info->pending_submit_list.next, + struct urb_priv, list); + if (!xenhcd_do_request(info, urbp)) + list_move_tail(&urbp->list, &info->in_progress_list); + else + xenhcd_giveback_urb(info, urbp->urb, -ESHUTDOWN); + } + xenhcd_timer_action_done(info, TIMER_SCAN_PENDING_URBS); +} + +/* + * caller must lock info->lock + */ +static void xenhcd_cancel_all_enqueued_urbs(struct xenhcd_info *info) +{ + struct urb_priv *urbp, *tmp; + int req_id; + + list_for_each_entry_safe(urbp, tmp, &info->in_progress_list, list) { + req_id = urbp->req_id; + if (!urbp->unlinked) { + xenhcd_gnttab_done(&info->shadow[req_id]); + if (urbp->urb->status == -EINPROGRESS) + /* not dequeued */ + xenhcd_giveback_urb(info, urbp->urb, + -ESHUTDOWN); + else /* dequeued */ + xenhcd_giveback_urb(info, urbp->urb, + urbp->urb->status); + } + info->shadow[req_id].urb = NULL; + } + + list_for_each_entry_safe(urbp, tmp, &info->pending_submit_list, list) + xenhcd_giveback_urb(info, urbp->urb, -ESHUTDOWN); +} + +/* + * caller must lock info->lock + */ +static void xenhcd_giveback_unlinked_urbs(struct xenhcd_info *info) +{ + struct urb_priv *urbp, *tmp; + + list_for_each_entry_safe(urbp, tmp, &info->giveback_waiting_list, list) + xenhcd_giveback_urb(info, urbp->urb, urbp->urb->status); +} + +static int xenhcd_submit_urb(struct xenhcd_info *info, struct urb_priv *urbp) +{ + int ret; + + if (RING_FULL(&info->urb_ring)) { + list_add_tail(&urbp->list, &info->pending_submit_list); + xenhcd_timer_action(info, TIMER_RING_WATCHDOG); + return 0; + } + + if (!list_empty(&info->pending_submit_list)) { + list_add_tail(&urbp->list, &info->pending_submit_list); + xenhcd_timer_action(info, TIMER_SCAN_PENDING_URBS); + return 0; + } + + ret = xenhcd_do_request(info, urbp); + if (ret == 0) + list_add_tail(&urbp->list, &info->in_progress_list); + + return ret; +} + +static int xenhcd_unlink_urb(struct xenhcd_info *info, struct urb_priv *urbp) +{ + int ret; + + /* already unlinked? */ + if (urbp->unlinked) + return -EBUSY; + + urbp->unlinked = true; + + /* the urb is still in pending_submit queue */ + if (urbp->req_id == ~0) { + list_move_tail(&urbp->list, &info->giveback_waiting_list); + xenhcd_timer_action(info, TIMER_SCAN_PENDING_URBS); + return 0; + } + + /* send unlink request to backend */ + if (RING_FULL(&info->urb_ring)) { + list_move_tail(&urbp->list, &info->pending_unlink_list); + xenhcd_timer_action(info, TIMER_RING_WATCHDOG); + return 0; + } + + if (!list_empty(&info->pending_unlink_list)) { + list_move_tail(&urbp->list, &info->pending_unlink_list); + xenhcd_timer_action(info, TIMER_SCAN_PENDING_URBS); + return 0; + } + + ret = xenhcd_do_request(info, urbp); + if (ret == 0) + list_move_tail(&urbp->list, &info->in_progress_list); + + return ret; +} + +static int xenhcd_urb_request_done(struct xenhcd_info *info) +{ + struct xenusb_urb_response res; + struct urb *urb; + RING_IDX i, rp; + __u16 id; + int more_to_do = 0; + unsigned long flags; + + spin_lock_irqsave(&info->lock, flags); + + rp = info->urb_ring.sring->rsp_prod; + if (RING_RESPONSE_PROD_OVERFLOW(&info->urb_ring, rp)) { + xenhcd_set_error(info, "Illegal index on urb-ring"); + spin_unlock_irqrestore(&info->lock, flags); + return 0; + } + rmb(); /* ensure we see queued responses up to "rp" */ + + for (i = info->urb_ring.rsp_cons; i != rp; i++) { + RING_COPY_RESPONSE(&info->urb_ring, i, &res); + id = res.id; + if (id >= XENUSB_URB_RING_SIZE) { + xenhcd_set_error(info, "Illegal data on urb-ring"); + continue; + } + + if (likely(xenusb_pipesubmit(info->shadow[id].req.pipe))) { + xenhcd_gnttab_done(&info->shadow[id]); + urb = info->shadow[id].urb; + if (likely(urb)) { + urb->actual_length = res.actual_length; + urb->error_count = res.error_count; + urb->start_frame = res.start_frame; + xenhcd_giveback_urb(info, urb, res.status); + } + } + + xenhcd_add_id_to_freelist(info, id); + } + info->urb_ring.rsp_cons = i; + + if (i != info->urb_ring.req_prod_pvt) + RING_FINAL_CHECK_FOR_RESPONSES(&info->urb_ring, more_to_do); + else + info->urb_ring.sring->rsp_event = i + 1; + + spin_unlock_irqrestore(&info->lock, flags); + + return more_to_do; +} + +static int xenhcd_conn_notify(struct xenhcd_info *info) +{ + struct xenusb_conn_response res; + struct xenusb_conn_request *req; + RING_IDX rc, rp; + __u16 id; + __u8 portnum, speed; + int more_to_do = 0; + int notify; + int port_changed = 0; + unsigned long flags; + + spin_lock_irqsave(&info->lock, flags); + + rc = info->conn_ring.rsp_cons; + rp = info->conn_ring.sring->rsp_prod; + if (RING_RESPONSE_PROD_OVERFLOW(&info->conn_ring, rp)) { + xenhcd_set_error(info, "Illegal index on conn-ring"); + spin_unlock_irqrestore(&info->lock, flags); + return 0; + } + rmb(); /* ensure we see queued responses up to "rp" */ + + while (rc != rp) { + RING_COPY_RESPONSE(&info->conn_ring, rc, &res); + id = res.id; + portnum = res.portnum; + speed = res.speed; + info->conn_ring.rsp_cons = ++rc; + + if (xenhcd_rhport_connect(info, portnum, speed)) { + xenhcd_set_error(info, "Illegal data on conn-ring"); + spin_unlock_irqrestore(&info->lock, flags); + return 0; + } + + if (info->ports[portnum - 1].c_connection) + port_changed = 1; + + barrier(); + + req = RING_GET_REQUEST(&info->conn_ring, + info->conn_ring.req_prod_pvt); + req->id = id; + info->conn_ring.req_prod_pvt++; + } + + if (rc != info->conn_ring.req_prod_pvt) + RING_FINAL_CHECK_FOR_RESPONSES(&info->conn_ring, more_to_do); + else + info->conn_ring.sring->rsp_event = rc + 1; + + RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&info->conn_ring, notify); + if (notify) + notify_remote_via_irq(info->irq); + + spin_unlock_irqrestore(&info->lock, flags); + + if (port_changed) + usb_hcd_poll_rh_status(xenhcd_info_to_hcd(info)); + + return more_to_do; +} + +static irqreturn_t xenhcd_int(int irq, void *dev_id) +{ + struct xenhcd_info *info = (struct xenhcd_info *)dev_id; + + if (unlikely(info->error)) + return IRQ_HANDLED; + + while (xenhcd_urb_request_done(info) | xenhcd_conn_notify(info)) + /* Yield point for this unbounded loop. */ + cond_resched(); + + return IRQ_HANDLED; +} + +static void xenhcd_destroy_rings(struct xenhcd_info *info) +{ + if (info->irq) + unbind_from_irqhandler(info->irq, info); + info->irq = 0; + + if (info->urb_ring_ref != GRANT_INVALID_REF) { + gnttab_end_foreign_access(info->urb_ring_ref, 0, + (unsigned long)info->urb_ring.sring); + info->urb_ring_ref = GRANT_INVALID_REF; + } + info->urb_ring.sring = NULL; + + if (info->conn_ring_ref != GRANT_INVALID_REF) { + gnttab_end_foreign_access(info->conn_ring_ref, 0, + (unsigned long)info->conn_ring.sring); + info->conn_ring_ref = GRANT_INVALID_REF; + } + info->conn_ring.sring = NULL; +} + +static int xenhcd_setup_rings(struct xenbus_device *dev, + struct xenhcd_info *info) +{ + struct xenusb_urb_sring *urb_sring; + struct xenusb_conn_sring *conn_sring; + grant_ref_t gref; + int err; + + info->urb_ring_ref = GRANT_INVALID_REF; + info->conn_ring_ref = GRANT_INVALID_REF; + + urb_sring = (struct xenusb_urb_sring *)get_zeroed_page( + GFP_NOIO | __GFP_HIGH); + if (!urb_sring) { + xenbus_dev_fatal(dev, -ENOMEM, "allocating urb ring"); + return -ENOMEM; + } + SHARED_RING_INIT(urb_sring); + FRONT_RING_INIT(&info->urb_ring, urb_sring, PAGE_SIZE); + + err = xenbus_grant_ring(dev, urb_sring, 1, &gref); + if (err < 0) { + free_page((unsigned long)urb_sring); + info->urb_ring.sring = NULL; + goto fail; + } + info->urb_ring_ref = gref; + + conn_sring = (struct xenusb_conn_sring *)get_zeroed_page( + GFP_NOIO | __GFP_HIGH); + if (!conn_sring) { + xenbus_dev_fatal(dev, -ENOMEM, "allocating conn ring"); + err = -ENOMEM; + goto fail; + } + SHARED_RING_INIT(conn_sring); + FRONT_RING_INIT(&info->conn_ring, conn_sring, PAGE_SIZE); + + err = xenbus_grant_ring(dev, conn_sring, 1, &gref); + if (err < 0) { + free_page((unsigned long)conn_sring); + info->conn_ring.sring = NULL; + goto fail; + } + info->conn_ring_ref = gref; + + err = xenbus_alloc_evtchn(dev, &info->evtchn); + if (err) { + xenbus_dev_fatal(dev, err, "xenbus_alloc_evtchn"); + goto fail; + } + + err = bind_evtchn_to_irq(info->evtchn); + if (err <= 0) { + xenbus_dev_fatal(dev, err, "bind_evtchn_to_irq"); + goto fail; + } + + info->irq = err; + + err = request_threaded_irq(info->irq, NULL, xenhcd_int, + IRQF_ONESHOT, "xenhcd", info); + if (err) { + xenbus_dev_fatal(dev, err, "request_threaded_irq"); + goto free_irq; + } + + return 0; + +free_irq: + unbind_from_irqhandler(info->irq, info); +fail: + xenhcd_destroy_rings(info); + return err; +} + +static int xenhcd_talk_to_backend(struct xenbus_device *dev, + struct xenhcd_info *info) +{ + const char *message; + struct xenbus_transaction xbt; + int err; + + err = xenhcd_setup_rings(dev, info); + if (err) + return err; + +again: + err = xenbus_transaction_start(&xbt); + if (err) { + xenbus_dev_fatal(dev, err, "starting transaction"); + goto destroy_ring; + } + + err = xenbus_printf(xbt, dev->nodename, "urb-ring-ref", "%u", + info->urb_ring_ref); + if (err) { + message = "writing urb-ring-ref"; + goto abort_transaction; + } + + err = xenbus_printf(xbt, dev->nodename, "conn-ring-ref", "%u", + info->conn_ring_ref); + if (err) { + message = "writing conn-ring-ref"; + goto abort_transaction; + } + + err = xenbus_printf(xbt, dev->nodename, "event-channel", "%u", + info->evtchn); + if (err) { + message = "writing event-channel"; + goto abort_transaction; + } + + err = xenbus_transaction_end(xbt, 0); + if (err) { + if (err == -EAGAIN) + goto again; + xenbus_dev_fatal(dev, err, "completing transaction"); + goto destroy_ring; + } + + return 0; + +abort_transaction: + xenbus_transaction_end(xbt, 1); + xenbus_dev_fatal(dev, err, "%s", message); + +destroy_ring: + xenhcd_destroy_rings(info); + + return err; +} + +static int xenhcd_connect(struct xenbus_device *dev) +{ + struct xenhcd_info *info = dev_get_drvdata(&dev->dev); + struct xenusb_conn_request *req; + int idx, err; + int notify; + char name[TASK_COMM_LEN]; + struct usb_hcd *hcd; + + hcd = xenhcd_info_to_hcd(info); + snprintf(name, TASK_COMM_LEN, "xenhcd.%d", hcd->self.busnum); + + err = xenhcd_talk_to_backend(dev, info); + if (err) + return err; + + /* prepare ring for hotplug notification */ + for (idx = 0; idx < XENUSB_CONN_RING_SIZE; idx++) { + req = RING_GET_REQUEST(&info->conn_ring, idx); + req->id = idx; + } + info->conn_ring.req_prod_pvt = idx; + + RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&info->conn_ring, notify); + if (notify) + notify_remote_via_irq(info->irq); + + return 0; +} + +static void xenhcd_disconnect(struct xenbus_device *dev) +{ + struct xenhcd_info *info = dev_get_drvdata(&dev->dev); + struct usb_hcd *hcd = xenhcd_info_to_hcd(info); + + usb_remove_hcd(hcd); + xenbus_frontend_closed(dev); +} + +static void xenhcd_watchdog(struct timer_list *timer) +{ + struct xenhcd_info *info = from_timer(info, timer, watchdog); + unsigned long flags; + + spin_lock_irqsave(&info->lock, flags); + if (likely(HC_IS_RUNNING(xenhcd_info_to_hcd(info)->state))) { + xenhcd_timer_action_done(info, TIMER_RING_WATCHDOG); + xenhcd_giveback_unlinked_urbs(info); + xenhcd_kick_pending_urbs(info); + } + spin_unlock_irqrestore(&info->lock, flags); +} + +/* + * one-time HC init + */ +static int xenhcd_setup(struct usb_hcd *hcd) +{ + struct xenhcd_info *info = xenhcd_hcd_to_info(hcd); + + spin_lock_init(&info->lock); + INIT_LIST_HEAD(&info->pending_submit_list); + INIT_LIST_HEAD(&info->pending_unlink_list); + INIT_LIST_HEAD(&info->in_progress_list); + INIT_LIST_HEAD(&info->giveback_waiting_list); + timer_setup(&info->watchdog, xenhcd_watchdog, 0); + + hcd->has_tt = (hcd->driver->flags & HCD_MASK) != HCD_USB11; + + return 0; +} + +/* + * start HC running + */ +static int xenhcd_run(struct usb_hcd *hcd) +{ + hcd->uses_new_polling = 1; + clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); + hcd->state = HC_STATE_RUNNING; + return 0; +} + +/* + * stop running HC + */ +static void xenhcd_stop(struct usb_hcd *hcd) +{ + struct xenhcd_info *info = xenhcd_hcd_to_info(hcd); + + del_timer_sync(&info->watchdog); + spin_lock_irq(&info->lock); + /* cancel all urbs */ + hcd->state = HC_STATE_HALT; + xenhcd_cancel_all_enqueued_urbs(info); + xenhcd_giveback_unlinked_urbs(info); + spin_unlock_irq(&info->lock); +} + +/* + * called as .urb_enqueue() + * non-error returns are promise to giveback the urb later + */ +static int xenhcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct xenhcd_info *info = xenhcd_hcd_to_info(hcd); + struct urb_priv *urbp; + unsigned long flags; + int ret; + + if (unlikely(info->error)) + return -ESHUTDOWN; + + urbp = kmem_cache_zalloc(xenhcd_urbp_cachep, mem_flags); + if (!urbp) + return -ENOMEM; + + spin_lock_irqsave(&info->lock, flags); + + urbp->urb = urb; + urb->hcpriv = urbp; + urbp->req_id = ~0; + urbp->unlink_req_id = ~0; + INIT_LIST_HEAD(&urbp->list); + urbp->status = 1; + urb->unlinked = false; + + ret = xenhcd_submit_urb(info, urbp); + + if (ret) + xenhcd_free_urb_priv(urbp); + + spin_unlock_irqrestore(&info->lock, flags); + + return ret; +} + +/* + * called as .urb_dequeue() + */ +static int xenhcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct xenhcd_info *info = xenhcd_hcd_to_info(hcd); + struct urb_priv *urbp; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&info->lock, flags); + + urbp = urb->hcpriv; + if (urbp) { + urbp->status = status; + ret = xenhcd_unlink_urb(info, urbp); + } + + spin_unlock_irqrestore(&info->lock, flags); + + return ret; +} + +/* + * called from usb_get_current_frame_number(), + * but, almost all drivers not use such function. + */ +static int xenhcd_get_frame(struct usb_hcd *hcd) +{ + /* it means error, but probably no problem :-) */ + return 0; +} + +static struct hc_driver xenhcd_usb20_hc_driver = { + .description = "xen-hcd", + .product_desc = "Xen USB2.0 Virtual Host Controller", + .hcd_priv_size = sizeof(struct xenhcd_info), + .flags = HCD_USB2, + + /* basic HC lifecycle operations */ + .reset = xenhcd_setup, + .start = xenhcd_run, + .stop = xenhcd_stop, + + /* managing urb I/O */ + .urb_enqueue = xenhcd_urb_enqueue, + .urb_dequeue = xenhcd_urb_dequeue, + .get_frame_number = xenhcd_get_frame, + + /* root hub operations */ + .hub_status_data = xenhcd_hub_status_data, + .hub_control = xenhcd_hub_control, +#ifdef CONFIG_PM + .bus_suspend = xenhcd_bus_suspend, + .bus_resume = xenhcd_bus_resume, +#endif +}; + +static struct hc_driver xenhcd_usb11_hc_driver = { + .description = "xen-hcd", + .product_desc = "Xen USB1.1 Virtual Host Controller", + .hcd_priv_size = sizeof(struct xenhcd_info), + .flags = HCD_USB11, + + /* basic HC lifecycle operations */ + .reset = xenhcd_setup, + .start = xenhcd_run, + .stop = xenhcd_stop, + + /* managing urb I/O */ + .urb_enqueue = xenhcd_urb_enqueue, + .urb_dequeue = xenhcd_urb_dequeue, + .get_frame_number = xenhcd_get_frame, + + /* root hub operations */ + .hub_status_data = xenhcd_hub_status_data, + .hub_control = xenhcd_hub_control, +#ifdef CONFIG_PM + .bus_suspend = xenhcd_bus_suspend, + .bus_resume = xenhcd_bus_resume, +#endif +}; + +static struct usb_hcd *xenhcd_create_hcd(struct xenbus_device *dev) +{ + int i; + int err = 0; + int num_ports; + int usb_ver; + struct usb_hcd *hcd = NULL; + struct xenhcd_info *info; + + err = xenbus_scanf(XBT_NIL, dev->otherend, "num-ports", "%d", + &num_ports); + if (err != 1) { + xenbus_dev_fatal(dev, err, "reading num-ports"); + return ERR_PTR(-EINVAL); + } + if (num_ports < 1 || num_ports > XENUSB_MAX_PORTNR) { + xenbus_dev_fatal(dev, err, "invalid num-ports"); + return ERR_PTR(-EINVAL); + } + + err = xenbus_scanf(XBT_NIL, dev->otherend, "usb-ver", "%d", &usb_ver); + if (err != 1) { + xenbus_dev_fatal(dev, err, "reading usb-ver"); + return ERR_PTR(-EINVAL); + } + switch (usb_ver) { + case XENUSB_VER_USB11: + hcd = usb_create_hcd(&xenhcd_usb11_hc_driver, &dev->dev, + dev_name(&dev->dev)); + break; + case XENUSB_VER_USB20: + hcd = usb_create_hcd(&xenhcd_usb20_hc_driver, &dev->dev, + dev_name(&dev->dev)); + break; + default: + xenbus_dev_fatal(dev, err, "invalid usb-ver"); + return ERR_PTR(-EINVAL); + } + if (!hcd) { + xenbus_dev_fatal(dev, err, + "fail to allocate USB host controller"); + return ERR_PTR(-ENOMEM); + } + + info = xenhcd_hcd_to_info(hcd); + info->xbdev = dev; + info->rh_numports = num_ports; + + for (i = 0; i < XENUSB_URB_RING_SIZE; i++) { + info->shadow[i].req.id = i + 1; + info->shadow[i].urb = NULL; + } + info->shadow[XENUSB_URB_RING_SIZE - 1].req.id = 0x0fff; + + return hcd; +} + +static void xenhcd_backend_changed(struct xenbus_device *dev, + enum xenbus_state backend_state) +{ + switch (backend_state) { + case XenbusStateInitialising: + case XenbusStateReconfiguring: + case XenbusStateReconfigured: + case XenbusStateUnknown: + break; + + case XenbusStateInitWait: + case XenbusStateInitialised: + case XenbusStateConnected: + if (dev->state != XenbusStateInitialising) + break; + if (!xenhcd_connect(dev)) + xenbus_switch_state(dev, XenbusStateConnected); + break; + + case XenbusStateClosed: + if (dev->state == XenbusStateClosed) + break; + fallthrough; /* Missed the backend's Closing state. */ + case XenbusStateClosing: + xenhcd_disconnect(dev); + break; + + default: + xenbus_dev_fatal(dev, -EINVAL, "saw state %d at frontend", + backend_state); + break; + } +} + +static int xenhcd_remove(struct xenbus_device *dev) +{ + struct xenhcd_info *info = dev_get_drvdata(&dev->dev); + struct usb_hcd *hcd = xenhcd_info_to_hcd(info); + + xenhcd_destroy_rings(info); + usb_put_hcd(hcd); + + return 0; +} + +static int xenhcd_probe(struct xenbus_device *dev, + const struct xenbus_device_id *id) +{ + int err; + struct usb_hcd *hcd; + struct xenhcd_info *info; + + if (usb_disabled()) + return -ENODEV; + + hcd = xenhcd_create_hcd(dev); + if (IS_ERR(hcd)) { + err = PTR_ERR(hcd); + xenbus_dev_fatal(dev, err, + "fail to create usb host controller"); + return err; + } + + info = xenhcd_hcd_to_info(hcd); + dev_set_drvdata(&dev->dev, info); + + err = usb_add_hcd(hcd, 0, 0); + if (err) { + xenbus_dev_fatal(dev, err, "fail to add USB host controller"); + usb_put_hcd(hcd); + dev_set_drvdata(&dev->dev, NULL); + } + + return err; +} + +static const struct xenbus_device_id xenhcd_ids[] = { + { "vusb" }, + { "" }, +}; + +static struct xenbus_driver xenhcd_driver = { + .ids = xenhcd_ids, + .probe = xenhcd_probe, + .otherend_changed = xenhcd_backend_changed, + .remove = xenhcd_remove, +}; + +static int __init xenhcd_init(void) +{ + if (!xen_domain()) + return -ENODEV; + + xenhcd_urbp_cachep = kmem_cache_create("xenhcd_urb_priv", + sizeof(struct urb_priv), 0, 0, NULL); + if (!xenhcd_urbp_cachep) { + pr_err("xenhcd failed to create kmem cache\n"); + return -ENOMEM; + } + + return xenbus_register_frontend(&xenhcd_driver); +} +module_init(xenhcd_init); + +static void __exit xenhcd_exit(void) +{ + kmem_cache_destroy(xenhcd_urbp_cachep); + xenbus_unregister_driver(&xenhcd_driver); +} +module_exit(xenhcd_exit); + +MODULE_ALIAS("xen:vusb"); +MODULE_AUTHOR("Juergen Gross <jgross@suse.com>"); +MODULE_DESCRIPTION("Xen USB Virtual Host Controller driver (xen-hcd)"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c index 58a0eae4f41b..91738af0ab14 100644 --- a/drivers/usb/host/xhci-mtk.c +++ b/drivers/usb/host/xhci-mtk.c @@ -245,11 +245,12 @@ static int xhci_mtk_host_disable(struct xhci_hcd_mtk *mtk) /* wait for host ip to sleep */ ret = readl_poll_timeout(&ippc->ip_pw_sts1, value, (value & STS1_IP_SLEEP_STS), 100, 100000); - if (ret) { + if (ret) dev_err(mtk->dev, "ip sleep failed!!!\n"); - return ret; - } - return 0; + else /* workaound for platforms using low level latch */ + usleep_range(100, 200); + + return ret; } static int xhci_mtk_ssusb_config(struct xhci_hcd_mtk *mtk) @@ -300,7 +301,7 @@ static void usb_wakeup_ip_sleep_set(struct xhci_hcd_mtk *mtk, bool enable) case SSUSB_UWK_V1_1: reg = mtk->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 = mtk->uwk_reg_base + PERI_WK_CTRL0; @@ -437,11 +438,8 @@ static int xhci_mtk_setup(struct usb_hcd *hcd) if (ret) return ret; - if (usb_hcd_is_primary_hcd(hcd)) { + if (usb_hcd_is_primary_hcd(hcd)) ret = xhci_mtk_sch_init(mtk); - if (ret) - return ret; - } return ret; } diff --git a/drivers/usb/misc/ehset.c b/drivers/usb/misc/ehset.c index f87890f9cd26..986d6589f053 100644 --- a/drivers/usb/misc/ehset.c +++ b/drivers/usb/misc/ehset.c @@ -18,6 +18,52 @@ #define TEST_SINGLE_STEP_GET_DEV_DESC 0x0107 #define TEST_SINGLE_STEP_SET_FEATURE 0x0108 +extern const struct usb_device_id *usb_device_match_id(struct usb_device *udev, + const struct usb_device_id *id); + +/* + * A list of USB hubs which requires to disable the power + * to the port before starting the testing procedures. + */ +static const struct usb_device_id ehset_hub_list[] = { + { USB_DEVICE(0x0424, 0x4502) }, + { USB_DEVICE(0x0424, 0x4913) }, + { USB_DEVICE(0x0451, 0x8027) }, + { } +}; + +static int ehset_prepare_port_for_testing(struct usb_device *hub_udev, u16 portnum) +{ + int ret = 0; + + /* + * The USB2.0 spec chapter 11.24.2.13 says that the USB port which is + * going under test needs to be put in suspend before sending the + * test command. Most hubs don't enforce this precondition, but there + * are some hubs which needs to disable the power to the port before + * starting the test. + */ + if (usb_device_match_id(hub_udev, ehset_hub_list)) { + ret = usb_control_msg_send(hub_udev, 0, USB_REQ_CLEAR_FEATURE, + USB_RT_PORT, USB_PORT_FEAT_ENABLE, + portnum, NULL, 0, 1000, GFP_KERNEL); + /* + * Wait for the port to be disabled. It's an arbitrary value + * which worked every time. + */ + msleep(100); + } else { + /* + * For the hubs which are compliant with the spec, + * put the port in SUSPEND. + */ + ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE, + USB_RT_PORT, USB_PORT_FEAT_SUSPEND, + portnum, NULL, 0, 1000, GFP_KERNEL); + } + return ret; +} + static int ehset_probe(struct usb_interface *intf, const struct usb_device_id *id) { @@ -30,24 +76,36 @@ static int ehset_probe(struct usb_interface *intf, switch (test_pid) { case TEST_SE0_NAK_PID: + ret = ehset_prepare_port_for_testing(hub_udev, portnum); + if (!ret) + break; ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE, USB_RT_PORT, USB_PORT_FEAT_TEST, (USB_TEST_SE0_NAK << 8) | portnum, NULL, 0, 1000, GFP_KERNEL); break; case TEST_J_PID: + ret = ehset_prepare_port_for_testing(hub_udev, portnum); + if (!ret) + break; ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE, USB_RT_PORT, USB_PORT_FEAT_TEST, (USB_TEST_J << 8) | portnum, NULL, 0, 1000, GFP_KERNEL); break; case TEST_K_PID: + ret = ehset_prepare_port_for_testing(hub_udev, portnum); + if (!ret) + break; ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE, USB_RT_PORT, USB_PORT_FEAT_TEST, (USB_TEST_K << 8) | portnum, NULL, 0, 1000, GFP_KERNEL); break; case TEST_PACKET_PID: + ret = ehset_prepare_port_for_testing(hub_udev, portnum); + if (!ret) + break; ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE, USB_RT_PORT, USB_PORT_FEAT_TEST, (USB_TEST_PACKET << 8) | portnum, diff --git a/drivers/usb/misc/ftdi-elan.c b/drivers/usb/misc/ftdi-elan.c index e5a8fcdbb78e..6c38c62d29b2 100644 --- a/drivers/usb/misc/ftdi-elan.c +++ b/drivers/usb/misc/ftdi-elan.c @@ -202,6 +202,7 @@ static void ftdi_elan_delete(struct kref *kref) mutex_unlock(&ftdi_module_lock); kfree(ftdi->bulk_in_buffer); ftdi->bulk_in_buffer = NULL; + kfree(ftdi); } static void ftdi_elan_put_kref(struct usb_ftdi *ftdi) diff --git a/drivers/usb/musb/am35x.c b/drivers/usb/musb/am35x.c index 660641ab1545..bf2c0fa6cb32 100644 --- a/drivers/usb/musb/am35x.c +++ b/drivers/usb/musb/am35x.c @@ -500,6 +500,8 @@ static int am35x_probe(struct platform_device *pdev) pinfo.num_res = pdev->num_resources; pinfo.data = pdata; pinfo.size_data = sizeof(*pdata); + pinfo.fwnode = of_fwnode_handle(pdev->dev.of_node); + pinfo.of_node_reused = true; glue->musb = musb = platform_device_register_full(&pinfo); if (IS_ERR(musb)) { diff --git a/drivers/usb/musb/da8xx.c b/drivers/usb/musb/da8xx.c index 1c023c0091c4..fd4ae2dd24e5 100644 --- a/drivers/usb/musb/da8xx.c +++ b/drivers/usb/musb/da8xx.c @@ -505,7 +505,6 @@ static struct of_dev_auxdata da8xx_auxdata_lookup[] = { static int da8xx_probe(struct platform_device *pdev) { - struct resource musb_resources[2]; struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev); struct da8xx_glue *glue; struct platform_device_info pinfo; @@ -558,25 +557,14 @@ static int da8xx_probe(struct platform_device *pdev) if (ret) return ret; - memset(musb_resources, 0x00, sizeof(*musb_resources) * - ARRAY_SIZE(musb_resources)); - - musb_resources[0].name = pdev->resource[0].name; - musb_resources[0].start = pdev->resource[0].start; - musb_resources[0].end = pdev->resource[0].end; - musb_resources[0].flags = pdev->resource[0].flags; - - musb_resources[1].name = pdev->resource[1].name; - musb_resources[1].start = pdev->resource[1].start; - musb_resources[1].end = pdev->resource[1].end; - musb_resources[1].flags = pdev->resource[1].flags; - pinfo = da8xx_dev_info; pinfo.parent = &pdev->dev; - pinfo.res = musb_resources; - pinfo.num_res = ARRAY_SIZE(musb_resources); + pinfo.res = pdev->resource; + pinfo.num_res = pdev->num_resources; pinfo.data = pdata; pinfo.size_data = sizeof(*pdata); + pinfo.fwnode = of_fwnode_handle(np); + pinfo.of_node_reused = true; glue->musb = platform_device_register_full(&pinfo); ret = PTR_ERR_OR_ZERO(glue->musb); diff --git a/drivers/usb/musb/jz4740.c b/drivers/usb/musb/jz4740.c index 5b7d576bf6ee..417c30bff9ca 100644 --- a/drivers/usb/musb/jz4740.c +++ b/drivers/usb/musb/jz4740.c @@ -231,6 +231,7 @@ static int jz4740_probe(struct platform_device *pdev) musb->dev.parent = dev; musb->dev.dma_mask = &musb->dev.coherent_dma_mask; musb->dev.coherent_dma_mask = DMA_BIT_MASK(32); + device_set_of_node_from_dev(&musb->dev, dev); glue->pdev = musb; glue->clk = clk; diff --git a/drivers/usb/musb/mediatek.c b/drivers/usb/musb/mediatek.c index f5d97eb84cb5..1aeb34dbe24f 100644 --- a/drivers/usb/musb/mediatek.c +++ b/drivers/usb/musb/mediatek.c @@ -538,6 +538,8 @@ static int mtk_musb_probe(struct platform_device *pdev) pinfo.num_res = pdev->num_resources; pinfo.data = pdata; pinfo.size_data = sizeof(*pdata); + pinfo.fwnode = of_fwnode_handle(np); + pinfo.of_node_reused = true; glue->musb_pdev = platform_device_register_full(&pinfo); if (IS_ERR(glue->musb_pdev)) { diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index f086960fe2b5..7d4d0713f4f0 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -301,7 +301,6 @@ static u64 omap2430_dmamask = DMA_BIT_MASK(32); static int omap2430_probe(struct platform_device *pdev) { - struct resource musb_resources[3]; struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev); struct omap_musb_board_data *data; struct platform_device *musb; @@ -328,6 +327,7 @@ static int omap2430_probe(struct platform_device *pdev) musb->dev.parent = &pdev->dev; musb->dev.dma_mask = &omap2430_dmamask; musb->dev.coherent_dma_mask = omap2430_dmamask; + device_set_of_node_from_dev(&musb->dev, &pdev->dev); glue->dev = &pdev->dev; glue->musb = musb; @@ -383,26 +383,7 @@ static int omap2430_probe(struct platform_device *pdev) INIT_WORK(&glue->omap_musb_mailbox_work, omap_musb_mailbox_work); - memset(musb_resources, 0x00, sizeof(*musb_resources) * - ARRAY_SIZE(musb_resources)); - - musb_resources[0].name = pdev->resource[0].name; - musb_resources[0].start = pdev->resource[0].start; - musb_resources[0].end = pdev->resource[0].end; - musb_resources[0].flags = pdev->resource[0].flags; - - musb_resources[1].name = pdev->resource[1].name; - musb_resources[1].start = pdev->resource[1].start; - musb_resources[1].end = pdev->resource[1].end; - musb_resources[1].flags = pdev->resource[1].flags; - - musb_resources[2].name = pdev->resource[2].name; - musb_resources[2].start = pdev->resource[2].start; - musb_resources[2].end = pdev->resource[2].end; - musb_resources[2].flags = pdev->resource[2].flags; - - ret = platform_device_add_resources(musb, musb_resources, - ARRAY_SIZE(musb_resources)); + ret = platform_device_add_resources(musb, pdev->resource, pdev->num_resources); if (ret) { dev_err(&pdev->dev, "failed to add resources\n"); goto err2; diff --git a/drivers/usb/musb/ux500.c b/drivers/usb/musb/ux500.c index 73538d1d0524..8ea62c344328 100644 --- a/drivers/usb/musb/ux500.c +++ b/drivers/usb/musb/ux500.c @@ -216,7 +216,6 @@ ux500_of_probe(struct platform_device *pdev, struct device_node *np) static int ux500_probe(struct platform_device *pdev) { - struct resource musb_resources[2]; struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev); struct device_node *np = pdev->dev.of_node; struct platform_device *musb; @@ -263,6 +262,7 @@ static int ux500_probe(struct platform_device *pdev) musb->dev.parent = &pdev->dev; musb->dev.dma_mask = &pdev->dev.coherent_dma_mask; musb->dev.coherent_dma_mask = pdev->dev.coherent_dma_mask; + device_set_of_node_from_dev(&musb->dev, &pdev->dev); glue->dev = &pdev->dev; glue->musb = musb; @@ -273,21 +273,7 @@ static int ux500_probe(struct platform_device *pdev) platform_set_drvdata(pdev, glue); - memset(musb_resources, 0x00, sizeof(*musb_resources) * - ARRAY_SIZE(musb_resources)); - - musb_resources[0].name = pdev->resource[0].name; - musb_resources[0].start = pdev->resource[0].start; - musb_resources[0].end = pdev->resource[0].end; - musb_resources[0].flags = pdev->resource[0].flags; - - musb_resources[1].name = pdev->resource[1].name; - musb_resources[1].start = pdev->resource[1].start; - musb_resources[1].end = pdev->resource[1].end; - musb_resources[1].flags = pdev->resource[1].flags; - - ret = platform_device_add_resources(musb, musb_resources, - ARRAY_SIZE(musb_resources)); + ret = platform_device_add_resources(musb, pdev->resource, pdev->num_resources); if (ret) { dev_err(&pdev->dev, "failed to add resources\n"); goto err2; diff --git a/drivers/usb/phy/phy-mv-usb.c b/drivers/usb/phy/phy-mv-usb.c index 576d925af77c..86503b7d695c 100644 --- a/drivers/usb/phy/phy-mv-usb.c +++ b/drivers/usb/phy/phy-mv-usb.c @@ -648,10 +648,8 @@ static int mv_otg_remove(struct platform_device *pdev) { struct mv_otg *mvotg = platform_get_drvdata(pdev); - if (mvotg->qwork) { - flush_workqueue(mvotg->qwork); + if (mvotg->qwork) destroy_workqueue(mvotg->qwork); - } mv_otg_disable(mvotg); @@ -825,7 +823,6 @@ static int mv_otg_probe(struct platform_device *pdev) err_disable_clk: mv_otg_disable_internal(mvotg); err_destroy_workqueue: - flush_workqueue(mvotg->qwork); destroy_workqueue(mvotg->qwork); return retval; diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 6aa28384f77f..9d6b7e02d6ef 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -303,6 +303,17 @@ static int ucsi_next_altmode(struct typec_altmode **alt) return -ENOENT; } +static int ucsi_get_num_altmode(struct typec_altmode **alt) +{ + int i; + + for (i = 0; i < UCSI_MAX_ALTMODES; i++) + if (!alt[i]) + break; + + return i; +} + static int ucsi_register_altmode(struct ucsi_connector *con, struct typec_altmode_desc *desc, u8 recipient) @@ -607,7 +618,7 @@ static int ucsi_get_src_pdos(struct ucsi_connector *con) static int ucsi_check_altmodes(struct ucsi_connector *con) { - int ret; + int ret, num_partner_am; ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_SOP); if (ret && ret != -ETIMEDOUT) @@ -617,6 +628,9 @@ static int ucsi_check_altmodes(struct ucsi_connector *con) /* Ignoring the errors in this case. */ if (con->partner_altmode[0]) { + num_partner_am = ucsi_get_num_altmode(con->partner_altmode); + if (num_partner_am > 0) + typec_partner_set_num_altmodes(con->partner, num_partner_am); ucsi_altmode_update_active(con); return 0; } diff --git a/drivers/usb/usbip/usbip_event.c b/drivers/usb/usbip/usbip_event.c index 086ca76dd053..26513540bcdb 100644 --- a/drivers/usb/usbip/usbip_event.c +++ b/drivers/usb/usbip/usbip_event.c @@ -137,7 +137,6 @@ int usbip_init_eh(void) void usbip_finish_eh(void) { - flush_workqueue(usbip_queue); destroy_workqueue(usbip_queue); usbip_queue = NULL; } diff --git a/include/xen/interface/io/usbif.h b/include/xen/interface/io/usbif.h new file mode 100644 index 000000000000..a70e0b93178b --- /dev/null +++ b/include/xen/interface/io/usbif.h @@ -0,0 +1,405 @@ +/* SPDX-License-Identifier: MIT */ + +/* + * usbif.h + * + * USB I/O interface for Xen guest OSes. + * + * Copyright (C) 2009, FUJITSU LABORATORIES LTD. + * Author: Noboru Iwamatsu <n_iwamatsu@jp.fujitsu.com> + */ + +#ifndef __XEN_PUBLIC_IO_USBIF_H__ +#define __XEN_PUBLIC_IO_USBIF_H__ + +#include "ring.h" +#include "../grant_table.h" + +/* + * Detailed Interface Description + * ============================== + * The pvUSB interface is using a split driver design: a frontend driver in + * the guest and a backend driver in a driver domain (normally dom0) having + * access to the physical USB device(s) being passed to the guest. + * + * The frontend and backend drivers use XenStore to initiate the connection + * between them, the I/O activity is handled via two shared ring pages and an + * event channel. As the interface between frontend and backend is at the USB + * host connector level, multiple (up to 31) physical USB devices can be + * handled by a single connection. + * + * The Xen pvUSB device name is "qusb", so the frontend's XenStore entries are + * to be found under "device/qusb", while the backend's XenStore entries are + * under "backend/<guest-dom-id>/qusb". + * + * When a new pvUSB connection is established, the frontend needs to setup the + * two shared ring pages for communication and the event channel. The ring + * pages need to be made available to the backend via the grant table + * interface. + * + * One of the shared ring pages is used by the backend to inform the frontend + * about USB device plug events (device to be added or removed). This is the + * "conn-ring". + * + * The other ring page is used for USB I/O communication (requests and + * responses). This is the "urb-ring". + * + * Feature and Parameter Negotiation + * ================================= + * The two halves of a Xen pvUSB driver utilize nodes within the XenStore to + * communicate capabilities and to negotiate operating parameters. This + * section enumerates these nodes which reside in the respective front and + * backend portions of the XenStore, following the XenBus convention. + * + * Any specified default value is in effect if the corresponding XenBus node + * is not present in the XenStore. + * + * XenStore nodes in sections marked "PRIVATE" are solely for use by the + * driver side whose XenBus tree contains them. + * + ***************************************************************************** + * Backend XenBus Nodes + ***************************************************************************** + * + *------------------ Backend Device Identification (PRIVATE) ------------------ + * + * num-ports + * Values: unsigned [1...31] + * + * Number of ports for this (virtual) USB host connector. + * + * usb-ver + * Values: unsigned [1...2] + * + * USB version of this host connector: 1 = USB 1.1, 2 = USB 2.0. + * + * port/[1...31] + * Values: string + * + * Physical USB device connected to the given port, e.g. "3-1.5". + * + ***************************************************************************** + * Frontend XenBus Nodes + ***************************************************************************** + * + *----------------------- Request Transport Parameters ----------------------- + * + * event-channel + * Values: unsigned + * + * The identifier of the Xen event channel used to signal activity + * in the ring buffer. + * + * urb-ring-ref + * Values: unsigned + * + * The Xen grant reference granting permission for the backend to map + * the sole page in a single page sized ring buffer. This is the ring + * buffer for urb requests. + * + * conn-ring-ref + * Values: unsigned + * + * The Xen grant reference granting permission for the backend to map + * the sole page in a single page sized ring buffer. This is the ring + * buffer for connection/disconnection requests. + * + * protocol + * Values: string (XEN_IO_PROTO_ABI_*) + * Default Value: XEN_IO_PROTO_ABI_NATIVE + * + * The machine ABI rules governing the format of all ring request and + * response structures. + * + * Protocol Description + * ==================== + * + *-------------------------- USB device plug events -------------------------- + * + * USB device plug events are send via the "conn-ring" shared page. As only + * events are being sent, the respective requests from the frontend to the + * backend are just dummy ones. + * The events sent to the frontend have the following layout: + * 0 1 2 3 octet + * +----------------+----------------+----------------+----------------+ + * | id | portnum | speed | 4 + * +----------------+----------------+----------------+----------------+ + * id - uint16_t, event id (taken from the actual frontend dummy request) + * portnum - uint8_t, port number (1 ... 31) + * speed - uint8_t, device XENUSB_SPEED_*, XENUSB_SPEED_NONE == unplug + * + * The dummy request: + * 0 1 octet + * +----------------+----------------+ + * | id | 2 + * +----------------+----------------+ + * id - uint16_t, guest supplied value (no need for being unique) + * + *-------------------------- USB I/O request --------------------------------- + * + * A single USB I/O request on the "urb-ring" has the following layout: + * 0 1 2 3 octet + * +----------------+----------------+----------------+----------------+ + * | id | nr_buffer_segs | 4 + * +----------------+----------------+----------------+----------------+ + * | pipe | 8 + * +----------------+----------------+----------------+----------------+ + * | transfer_flags | buffer_length | 12 + * +----------------+----------------+----------------+----------------+ + * | request type specific | 16 + * | data | 20 + * +----------------+----------------+----------------+----------------+ + * | seg[0] | 24 + * | data | 28 + * +----------------+----------------+----------------+----------------+ + * |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/| + * +----------------+----------------+----------------+----------------+ + * | seg[XENUSB_MAX_SEGMENTS_PER_REQUEST - 1] | 144 + * | data | 148 + * +----------------+----------------+----------------+----------------+ + * Bit field bit number 0 is always least significant bit, undefined bits must + * be zero. + * id - uint16_t, guest supplied value + * nr_buffer_segs - uint16_t, number of segment entries in seg[] array + * pipe - uint32_t, bit field with multiple information: + * bits 0-4: port request to send to + * bit 5: unlink request with specified id (cancel I/O) if set (see below) + * bit 7: direction (1 = read from device) + * bits 8-14: device number on port + * bits 15-18: endpoint of device + * bits 30-31: request type: 00 = isochronous, 01 = interrupt, + * 10 = control, 11 = bulk + * transfer_flags - uint16_t, bit field with processing flags: + * bit 0: less data than specified allowed + * buffer_length - uint16_t, total length of data + * request type specific data - 8 bytes, see below + * seg[] - array with 8 byte elements, see below + * + * Request type specific data for isochronous request: + * 0 1 2 3 octet + * +----------------+----------------+----------------+----------------+ + * | interval | start_frame | 4 + * +----------------+----------------+----------------+----------------+ + * | number_of_packets | nr_frame_desc_segs | 8 + * +----------------+----------------+----------------+----------------+ + * interval - uint16_t, time interval in msecs between frames + * start_frame - uint16_t, start frame number + * number_of_packets - uint16_t, number of packets to transfer + * nr_frame_desc_segs - uint16_t number of seg[] frame descriptors elements + * + * Request type specific data for interrupt request: + * 0 1 2 3 octet + * +----------------+----------------+----------------+----------------+ + * | interval | 0 | 4 + * +----------------+----------------+----------------+----------------+ + * | 0 | 8 + * +----------------+----------------+----------------+----------------+ + * interval - uint16_t, time in msecs until interruption + * + * Request type specific data for control request: + * 0 1 2 3 octet + * +----------------+----------------+----------------+----------------+ + * | data of setup packet | 4 + * | | 8 + * +----------------+----------------+----------------+----------------+ + * + * Request type specific data for bulk request: + * 0 1 2 3 octet + * +----------------+----------------+----------------+----------------+ + * | 0 | 4 + * | 0 | 8 + * +----------------+----------------+----------------+----------------+ + * + * Request type specific data for unlink request: + * 0 1 2 3 octet + * +----------------+----------------+----------------+----------------+ + * | unlink_id | 0 | 4 + * +----------------+----------------+----------------+----------------+ + * | 0 | 8 + * +----------------+----------------+----------------+----------------+ + * unlink_id - uint16_t, request id of request to terminate + * + * seg[] array element layout: + * 0 1 2 3 octet + * +----------------+----------------+----------------+----------------+ + * | gref | 4 + * +----------------+----------------+----------------+----------------+ + * | offset | length | 8 + * +----------------+----------------+----------------+----------------+ + * gref - uint32_t, grant reference of buffer page + * offset - uint16_t, offset of buffer start in page + * length - uint16_t, length of buffer in page + * + *-------------------------- USB I/O response -------------------------------- + * + * 0 1 2 3 octet + * +----------------+----------------+----------------+----------------+ + * | id | start_frame | 4 + * +----------------+----------------+----------------+----------------+ + * | status | 8 + * +----------------+----------------+----------------+----------------+ + * | actual_length | 12 + * +----------------+----------------+----------------+----------------+ + * | error_count | 16 + * +----------------+----------------+----------------+----------------+ + * id - uint16_t, id of the request this response belongs to + * start_frame - uint16_t, start_frame this response (iso requests only) + * status - int32_t, XENUSB_STATUS_* (non-iso requests) + * actual_length - uint32_t, actual size of data transferred + * error_count - uint32_t, number of errors (iso requests) + */ + +enum xenusb_spec_version { + XENUSB_VER_UNKNOWN = 0, + XENUSB_VER_USB11, + XENUSB_VER_USB20, + XENUSB_VER_USB30, /* not supported yet */ +}; + +/* + * USB pipe in xenusb_request + * + * - port number: bits 0-4 + * (USB_MAXCHILDREN is 31) + * + * - operation flag: bit 5 + * (0 = submit urb, + * 1 = unlink urb) + * + * - direction: bit 7 + * (0 = Host-to-Device [Out] + * 1 = Device-to-Host [In]) + * + * - device address: bits 8-14 + * + * - endpoint: bits 15-18 + * + * - pipe type: bits 30-31 + * (00 = isochronous, 01 = interrupt, + * 10 = control, 11 = bulk) + */ + +#define XENUSB_PIPE_PORT_MASK 0x0000001f +#define XENUSB_PIPE_UNLINK 0x00000020 +#define XENUSB_PIPE_DIR 0x00000080 +#define XENUSB_PIPE_DEV_MASK 0x0000007f +#define XENUSB_PIPE_DEV_SHIFT 8 +#define XENUSB_PIPE_EP_MASK 0x0000000f +#define XENUSB_PIPE_EP_SHIFT 15 +#define XENUSB_PIPE_TYPE_MASK 0x00000003 +#define XENUSB_PIPE_TYPE_SHIFT 30 +#define XENUSB_PIPE_TYPE_ISOC 0 +#define XENUSB_PIPE_TYPE_INT 1 +#define XENUSB_PIPE_TYPE_CTRL 2 +#define XENUSB_PIPE_TYPE_BULK 3 + +#define xenusb_pipeportnum(pipe) ((pipe) & XENUSB_PIPE_PORT_MASK) +#define xenusb_setportnum_pipe(pipe, portnum) ((pipe) | (portnum)) + +#define xenusb_pipeunlink(pipe) ((pipe) & XENUSB_PIPE_UNLINK) +#define xenusb_pipesubmit(pipe) (!xenusb_pipeunlink(pipe)) +#define xenusb_setunlink_pipe(pipe) ((pipe) | XENUSB_PIPE_UNLINK) + +#define xenusb_pipein(pipe) ((pipe) & XENUSB_PIPE_DIR) +#define xenusb_pipeout(pipe) (!xenusb_pipein(pipe)) + +#define xenusb_pipedevice(pipe) \ + (((pipe) >> XENUSB_PIPE_DEV_SHIFT) & XENUSB_PIPE_DEV_MASK) + +#define xenusb_pipeendpoint(pipe) \ + (((pipe) >> XENUSB_PIPE_EP_SHIFT) & XENUSB_PIPE_EP_MASK) + +#define xenusb_pipetype(pipe) \ + (((pipe) >> XENUSB_PIPE_TYPE_SHIFT) & XENUSB_PIPE_TYPE_MASK) +#define xenusb_pipeisoc(pipe) (xenusb_pipetype(pipe) == XENUSB_PIPE_TYPE_ISOC) +#define xenusb_pipeint(pipe) (xenusb_pipetype(pipe) == XENUSB_PIPE_TYPE_INT) +#define xenusb_pipectrl(pipe) (xenusb_pipetype(pipe) == XENUSB_PIPE_TYPE_CTRL) +#define xenusb_pipebulk(pipe) (xenusb_pipetype(pipe) == XENUSB_PIPE_TYPE_BULK) + +#define XENUSB_MAX_SEGMENTS_PER_REQUEST (16) +#define XENUSB_MAX_PORTNR 31 +#define XENUSB_RING_SIZE 4096 + +/* + * RING for transferring urbs. + */ +struct xenusb_request_segment { + grant_ref_t gref; + uint16_t offset; + uint16_t length; +}; + +struct xenusb_urb_request { + uint16_t id; /* request id */ + uint16_t nr_buffer_segs; /* number of urb->transfer_buffer segments */ + + /* basic urb parameter */ + uint32_t pipe; + uint16_t transfer_flags; +#define XENUSB_SHORT_NOT_OK 0x0001 + uint16_t buffer_length; + union { + uint8_t ctrl[8]; /* setup_packet (Ctrl) */ + + struct { + uint16_t interval; /* maximum (1024*8) in usb core */ + uint16_t start_frame; /* start frame */ + uint16_t number_of_packets; /* number of ISO packet */ + uint16_t nr_frame_desc_segs; /* number of iso_frame_desc segments */ + } isoc; + + struct { + uint16_t interval; /* maximum (1024*8) in usb core */ + uint16_t pad[3]; + } intr; + + struct { + uint16_t unlink_id; /* unlink request id */ + uint16_t pad[3]; + } unlink; + + } u; + + /* urb data segments */ + struct xenusb_request_segment seg[XENUSB_MAX_SEGMENTS_PER_REQUEST]; +}; + +struct xenusb_urb_response { + uint16_t id; /* request id */ + uint16_t start_frame; /* start frame (ISO) */ + int32_t status; /* status (non-ISO) */ +#define XENUSB_STATUS_OK 0 +#define XENUSB_STATUS_NODEV (-19) +#define XENUSB_STATUS_INVAL (-22) +#define XENUSB_STATUS_STALL (-32) +#define XENUSB_STATUS_IOERROR (-71) +#define XENUSB_STATUS_BABBLE (-75) +#define XENUSB_STATUS_SHUTDOWN (-108) + int32_t actual_length; /* actual transfer length */ + int32_t error_count; /* number of ISO errors */ +}; + +DEFINE_RING_TYPES(xenusb_urb, struct xenusb_urb_request, struct xenusb_urb_response); +#define XENUSB_URB_RING_SIZE __CONST_RING_SIZE(xenusb_urb, XENUSB_RING_SIZE) + +/* + * RING for notifying connect/disconnect events to frontend + */ +struct xenusb_conn_request { + uint16_t id; +}; + +struct xenusb_conn_response { + uint16_t id; /* request id */ + uint8_t portnum; /* port number */ + uint8_t speed; /* usb_device_speed */ +#define XENUSB_SPEED_NONE 0 +#define XENUSB_SPEED_LOW 1 +#define XENUSB_SPEED_FULL 2 +#define XENUSB_SPEED_HIGH 3 +}; + +DEFINE_RING_TYPES(xenusb_conn, struct xenusb_conn_request, struct xenusb_conn_response); +#define XENUSB_CONN_RING_SIZE __CONST_RING_SIZE(xenusb_conn, XENUSB_RING_SIZE) + +#endif /* __XEN_PUBLIC_IO_USBIF_H__ */ |