From 1fcf689181e95f3a289f42806fff38fb15a66b61 Mon Sep 17 00:00:00 2001 From: "Ahmed S. Darwish" Date: Mon, 26 Oct 2020 15:03:13 +0100 Subject: [PATCH 001/172] USB: serial: digi_acceleport: remove in_interrupt() usage The usage of in_interrupt() in drivers is phased out and Linus clearly requested that code which changes behaviour depending on context should either be separated or the context be conveyed in an argument passed by the caller, which usually knows the context. The debug printk() in digi_write() prints in_interrupt() as context information. This information is imprecise as it does not distinguish between hard-IRQ or disabled bottom half and it does not consider disabled interrupts or preemption. It is not really helpful. Remove the in_interrupt() printout. Signed-off-by: Ahmed S. Darwish Signed-off-by: Thomas Gleixner Signed-off-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20201026140313.dpg3hkhkje2os4hw@linutronix.de [ johan: amend commit message ] Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 91055a191995..016e7dec3196 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -911,9 +911,8 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, unsigned char *data = port->write_urb->transfer_buffer; unsigned long flags = 0; - dev_dbg(&port->dev, - "digi_write: TOP: port=%d, count=%d, in_interrupt=%ld\n", - priv->dp_port_num, count, in_interrupt()); + dev_dbg(&port->dev, "digi_write: TOP: port=%d, count=%d\n", + priv->dp_port_num, count); /* copy user data (which can sleep) before getting spin lock */ count = min(count, port->bulk_out_size-2); From d1849b9ff9f442b3720e07b2d6f6c5cf1b97b1a3 Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Mon, 26 Oct 2020 12:14:50 -0700 Subject: [PATCH 002/172] USB: serial: iuu_phoenix: remove unneeded break A break is not needed if it is preceded by a return. Signed-off-by: Tom Rix Signed-off-by: Johan Hovold --- drivers/usb/serial/iuu_phoenix.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/usb/serial/iuu_phoenix.c b/drivers/usb/serial/iuu_phoenix.c index b4ba79123d9d..f1201d4de297 100644 --- a/drivers/usb/serial/iuu_phoenix.c +++ b/drivers/usb/serial/iuu_phoenix.c @@ -850,7 +850,6 @@ static int iuu_uart_baud(struct usb_serial_port *port, u32 baud_base, default: kfree(dataout); return IUU_INVALID_PARAMETER; - break; } switch (parity & 0xF0) { @@ -864,7 +863,6 @@ static int iuu_uart_baud(struct usb_serial_port *port, u32 baud_base, default: kfree(dataout); return IUU_INVALID_PARAMETER; - break; } status = bulk_immediate(port, dataout, DataCount); From 23eac8531acdec3bdbd920d5ec0d7747508eaaab Mon Sep 17 00:00:00 2001 From: Sudip Mukherjee Date: Sun, 11 Oct 2020 21:50:08 +0100 Subject: [PATCH 003/172] usb: host: ehci-sched: add comment about find_tt() not returning error Add a comment explaining why find_tt() will not return error even though find_tt() is checking for NULL and other errors. Acked-by: Alan Stern Signed-off-by: Sudip Mukherjee Link: https://lore.kernel.org/r/20201011205008.24369-1-sudipm.mukherjee@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-sched.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index 6dfb242f9a4b..0f85aa9b2fb1 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -244,6 +244,12 @@ static void reserve_release_intr_bandwidth(struct ehci_hcd *ehci, /* FS/LS bus bandwidth */ if (tt_usecs) { + /* + * find_tt() will not return any error here as we have + * already called find_tt() before calling this function + * and checked for any error return. The previous call + * would have created the data structure. + */ tt = find_tt(qh->ps.udev); if (sign > 0) list_add_tail(&qh->ps.ps_list, &tt->ps_list); @@ -1337,6 +1343,12 @@ static void reserve_release_iso_bandwidth(struct ehci_hcd *ehci, } } + /* + * find_tt() will not return any error here as we have + * already called find_tt() before calling this function + * and checked for any error return. The previous call + * would have created the data structure. + */ tt = find_tt(stream->ps.udev); if (sign > 0) list_add_tail(&stream->ps.ps_list, &tt->ps_list); From 907412c0bb2340e699b5bd1eeafa9d597d2d3045 Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Mon, 26 Oct 2020 12:28:00 -0700 Subject: [PATCH 004/172] usb: misc: iowarrior: remove unneeded break A break is not needed if it is preceded by a goto. Signed-off-by: Tom Rix Link: https://lore.kernel.org/r/20201026192800.1431547-1-trix@redhat.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/iowarrior.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/usb/misc/iowarrior.c b/drivers/usb/misc/iowarrior.c index 70ec29681526..efbd317f2f25 100644 --- a/drivers/usb/misc/iowarrior.c +++ b/drivers/usb/misc/iowarrior.c @@ -384,7 +384,6 @@ static ssize_t iowarrior_write(struct file *file, retval = usb_set_report(dev->interface, 2, 0, buf, count); kfree(buf); goto exit; - break; case USB_DEVICE_ID_CODEMERCS_IOW56: case USB_DEVICE_ID_CODEMERCS_IOW56AM: case USB_DEVICE_ID_CODEMERCS_IOW28: @@ -454,14 +453,12 @@ static ssize_t iowarrior_write(struct file *file, retval = count; usb_free_urb(int_out_urb); goto exit; - break; default: /* what do we have here ? An unsupported Product-ID ? */ dev_err(&dev->interface->dev, "%s - not supported for product=0x%x\n", __func__, dev->product_id); retval = -EFAULT; goto exit; - break; } error: usb_free_coherent(dev->udev, dev->report_size, buf, From e9b0c20441294717c764c80c171f37c5a5d82ac9 Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Mon, 26 Oct 2020 12:04:57 -0700 Subject: [PATCH 005/172] usb: storage: freecom: remove unneeded break A break is not needed if it is preceded by a return. Signed-off-by: Tom Rix Link: https://lore.kernel.org/r/20201026190457.1428516-1-trix@redhat.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/freecom.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/storage/freecom.c b/drivers/usb/storage/freecom.c index 3d5f7d0ff0f1..2b098b55c4cb 100644 --- a/drivers/usb/storage/freecom.c +++ b/drivers/usb/storage/freecom.c @@ -431,7 +431,6 @@ static int freecom_transport(struct scsi_cmnd *srb, struct us_data *us) us->srb->sc_data_direction); /* Return fail, SCSI seems to handle this better. */ return USB_STOR_TRANSPORT_FAILED; - break; } return USB_STOR_TRANSPORT_GOOD; From 12cb474a0f8e78228a166a665f55147b7d79ae2e Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Mon, 26 Oct 2020 11:58:12 -0700 Subject: [PATCH 006/172] usb: host: xhci-mem: remove unneeded break A break is not needed if it is preceded by a return. Signed-off-by: Tom Rix Link: https://lore.kernel.org/r/20201026185812.1427461-1-trix@redhat.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index fe405cd38dbc..b46ef45c4d25 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1144,7 +1144,6 @@ int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *ud case USB_SPEED_WIRELESS: xhci_dbg(xhci, "FIXME xHCI doesn't support wireless speeds\n"); return -EINVAL; - break; default: /* Speed was set earlier, this shouldn't happen. */ return -EINVAL; From 9df556d774fda1eb9214c7ab3f1e5d0ec1265bce Mon Sep 17 00:00:00 2001 From: Al Cooper Date: Mon, 12 Oct 2020 16:00:06 -0400 Subject: [PATCH 007/172] dt-bindings: Add support for Broadcom USB pin map driver Add DT bindings for the Broadcom USB pin map driver. This driver allows some USB input and output signals to be mapped to any GPIO instead of the normal dedicated pins to/from the XHCI controller. Reviewed-by: Rob Herring Signed-off-by: Al Cooper Link: https://lore.kernel.org/r/20201012200007.8862-2-alcooperx@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/brcm,usb-pinmap.yaml | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/brcm,usb-pinmap.yaml diff --git a/Documentation/devicetree/bindings/usb/brcm,usb-pinmap.yaml b/Documentation/devicetree/bindings/usb/brcm,usb-pinmap.yaml new file mode 100644 index 000000000000..ffa148b9eaa8 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/brcm,usb-pinmap.yaml @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/brcm,usb-pinmap.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Broadcom USB pin map Controller Device Tree Bindings + +maintainers: + - Al Cooper + +properties: + compatible: + items: + - const: brcm,usb-pinmap + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + description: Interrupt for signals mirrored to out-gpios. + + in-gpios: + description: Array of one or two GPIO pins used for input signals. + + brcm,in-functions: + $ref: /schemas/types.yaml#/definitions/string-array + description: Array of input signal names, one per gpio in in-gpios. + + brcm,in-masks: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Array of enable and mask pairs, one per gpio in-gpios. + + out-gpios: + description: Array of one GPIO pin used for output signals. + + brcm,out-functions: + $ref: /schemas/types.yaml#/definitions/string-array + description: Array of output signal names, one per gpio in out-gpios. + + brcm,out-masks: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Array of enable, value, changed and clear masks, one + per gpio in out-gpios. + +required: + - compatible + - reg + +additionalProperties: false + +dependencies: + in-gpios: [ interrupts ] + +examples: + - | + usb_pinmap: usb-pinmap@22000d0 { + compatible = "brcm,usb-pinmap"; + reg = <0x22000d0 0x4>; + in-gpios = <&gpio 18 0>, <&gpio 19 0>; + brcm,in-functions = "VBUS", "PWRFLT"; + brcm,in-masks = <0x8000 0x40000 0x10000 0x80000>; + out-gpios = <&gpio 20 0>; + brcm,out-functions = "PWRON"; + brcm,out-masks = <0x20000 0x800000 0x400000 0x200000>; + interrupts = <0x0 0xb2 0x4>; + }; + +... From 517c4c44b32372d2fdf4421822e21083c45e89f9 Mon Sep 17 00:00:00 2001 From: Al Cooper Date: Mon, 12 Oct 2020 16:00:07 -0400 Subject: [PATCH 008/172] usb: Add driver to allow any GPIO to be used for 7211 USB signals The Broadcom 7211 has new functionality that allows some USB low speed side band signals, that go from the XHCI host controller to pins on the chip, to be remapped to use any GPIO pin instead of the limited set selectable by hardware. This can be done without changing the standard driver for the host controller. There is currently support for three USB signals, PWRON, VBUS_PRESENT and PWRFLT. This driver will allow the remapping of any of these three signals based on settings in the Device Tree node for the driver. The driver was written so that it could handle additional signals added in the future by just adding the correct properties to the DT node. Below is an example of a DT node that would remap all three signals: usb_pinmap: usb-pinmap@22000d0 { compatible = "brcm,usb-pinmap"; reg = <0x22000d0 0x4>; in-gpios = <&gpio 18 0>, <&gpio 19 0>; brcm,in-functions = "VBUS", "PWRFLT"; brcm,in-masks = <0x8000 0x40000 0x10000 0x80000>; out-gpios = <&gpio 20 0>; brcm,out-functions = "PWRON"; brcm,out-masks = <0x20000 0x800000 0x400000 0x200000>; interrupts = <0x0 0xb2 0x4>; }; Signed-off-by: Al Cooper Link: https://lore.kernel.org/r/20201012200007.8862-3-alcooperx@gmail.com Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 8 + drivers/usb/misc/Kconfig | 9 + drivers/usb/misc/Makefile | 1 + drivers/usb/misc/brcmstb-usb-pinmap.c | 351 ++++++++++++++++++++++++++ 4 files changed, 369 insertions(+) create mode 100644 drivers/usb/misc/brcmstb-usb-pinmap.c diff --git a/MAINTAINERS b/MAINTAINERS index e73636b75f29..a6818b37704d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3572,6 +3572,14 @@ S: Maintained F: Documentation/devicetree/bindings/usb/brcm,bcm7445-ehci.yaml F: drivers/usb/host/ehci-brcm.* +BROADCOM BRCMSTB USB PIN MAP DRIVER +M: Al Cooper +L: linux-usb@vger.kernel.org +L: bcm-kernel-feedback-list@broadcom.com +S: Maintained +F: Documentation/devicetree/bindings/usb/brcm,usb-pinmap.yaml +F: drivers/usb/misc/brcmstb-usb-pinmap.c + BROADCOM BRCMSTB USB2 and USB3 PHY DRIVER M: Al Cooper L: linux-kernel@vger.kernel.org diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 6818ea689cd9..8f1144359012 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -275,3 +275,12 @@ config USB_CHAOSKEY To compile this driver as a module, choose M here: the module will be called chaoskey. + +config BRCM_USB_PINMAP + tristate "Broadcom pinmap driver support" + depends on (ARCH_BRCMSTB && PHY_BRCM_USB) || COMPILE_TEST + default ARCH_BRCMSTB && PHY_BRCM_USB + help + This option enables support for remapping some USB external + signals, which are typically on dedicated pins on the chip, + to any gpio. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index da39bddb0604..5f4e598573ab 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -31,3 +31,4 @@ obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o +obj-$(CONFIG_BRCM_USB_PINMAP) += brcmstb-usb-pinmap.o diff --git a/drivers/usb/misc/brcmstb-usb-pinmap.c b/drivers/usb/misc/brcmstb-usb-pinmap.c new file mode 100644 index 000000000000..02144c39aaba --- /dev/null +++ b/drivers/usb/misc/brcmstb-usb-pinmap.c @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2020, Broadcom */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct out_pin { + u32 enable_mask; + u32 value_mask; + u32 changed_mask; + u32 clr_changed_mask; + struct gpio_desc *gpiod; + const char *name; +}; + +struct in_pin { + u32 enable_mask; + u32 value_mask; + struct gpio_desc *gpiod; + const char *name; + struct brcmstb_usb_pinmap_data *pdata; +}; + +struct brcmstb_usb_pinmap_data { + void __iomem *regs; + int in_count; + struct in_pin *in_pins; + int out_count; + struct out_pin *out_pins; +}; + + +static void pinmap_set(void __iomem *reg, u32 mask) +{ + u32 val; + + val = readl(reg); + val |= mask; + writel(val, reg); +} + +static void pinmap_unset(void __iomem *reg, u32 mask) +{ + u32 val; + + val = readl(reg); + val &= ~mask; + writel(val, reg); +} + +static void sync_in_pin(struct in_pin *pin) +{ + u32 val; + + val = gpiod_get_value(pin->gpiod); + if (val) + pinmap_set(pin->pdata->regs, pin->value_mask); + else + pinmap_unset(pin->pdata->regs, pin->value_mask); +} + +/* + * Interrupt from override register, propagate from override bit + * to GPIO. + */ +static irqreturn_t brcmstb_usb_pinmap_ovr_isr(int irq, void *dev_id) +{ + struct brcmstb_usb_pinmap_data *pdata = dev_id; + struct out_pin *pout; + u32 val; + u32 bit; + int x; + + pr_debug("%s: reg: 0x%x\n", __func__, readl(pdata->regs)); + pout = pdata->out_pins; + for (x = 0; x < pdata->out_count; x++) { + val = readl(pdata->regs); + if (val & pout->changed_mask) { + pinmap_set(pdata->regs, pout->clr_changed_mask); + pinmap_unset(pdata->regs, pout->clr_changed_mask); + bit = val & pout->value_mask; + gpiod_set_value(pout->gpiod, bit ? 1 : 0); + pr_debug("%s: %s bit changed state to %d\n", + __func__, pout->name, bit ? 1 : 0); + } + } + return IRQ_HANDLED; +} + +/* + * Interrupt from GPIO, propagate from GPIO to override bit. + */ +static irqreturn_t brcmstb_usb_pinmap_gpio_isr(int irq, void *dev_id) +{ + struct in_pin *pin = dev_id; + + pr_debug("%s: %s pin changed state\n", __func__, pin->name); + sync_in_pin(pin); + return IRQ_HANDLED; +} + + +static void get_pin_counts(struct device_node *dn, int *in_count, + int *out_count) +{ + int in; + int out; + + *in_count = 0; + *out_count = 0; + in = of_property_count_strings(dn, "brcm,in-functions"); + if (in < 0) + return; + out = of_property_count_strings(dn, "brcm,out-functions"); + if (out < 0) + return; + *in_count = in; + *out_count = out; +} + +static int parse_pins(struct device *dev, struct device_node *dn, + struct brcmstb_usb_pinmap_data *pdata) +{ + struct out_pin *pout; + struct in_pin *pin; + int index; + int res; + int x; + + pin = pdata->in_pins; + for (x = 0, index = 0; x < pdata->in_count; x++) { + pin->gpiod = devm_gpiod_get_index(dev, "in", x, GPIOD_IN); + if (IS_ERR(pin->gpiod)) { + dev_err(dev, "Error getting gpio %s\n", pin->name); + return PTR_ERR(pin->gpiod); + + } + res = of_property_read_string_index(dn, "brcm,in-functions", x, + &pin->name); + if (res < 0) { + dev_err(dev, "Error getting brcm,in-functions for %s\n", + pin->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,in-masks", index++, + &pin->enable_mask); + if (res < 0) { + dev_err(dev, "Error getting 1st brcm,in-masks for %s\n", + pin->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,in-masks", index++, + &pin->value_mask); + if (res < 0) { + dev_err(dev, "Error getting 2nd brcm,in-masks for %s\n", + pin->name); + return res; + } + pin->pdata = pdata; + pin++; + } + pout = pdata->out_pins; + for (x = 0, index = 0; x < pdata->out_count; x++) { + pout->gpiod = devm_gpiod_get_index(dev, "out", x, + GPIOD_OUT_HIGH); + if (IS_ERR(pout->gpiod)) { + dev_err(dev, "Error getting gpio %s\n", pin->name); + return PTR_ERR(pout->gpiod); + } + res = of_property_read_string_index(dn, "brcm,out-functions", x, + &pout->name); + if (res < 0) { + dev_err(dev, "Error getting brcm,out-functions for %s\n", + pout->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,out-masks", index++, + &pout->enable_mask); + if (res < 0) { + dev_err(dev, "Error getting 1st brcm,out-masks for %s\n", + pout->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,out-masks", index++, + &pout->value_mask); + if (res < 0) { + dev_err(dev, "Error getting 2nd brcm,out-masks for %s\n", + pout->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,out-masks", index++, + &pout->changed_mask); + if (res < 0) { + dev_err(dev, "Error getting 3rd brcm,out-masks for %s\n", + pout->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,out-masks", index++, + &pout->clr_changed_mask); + if (res < 0) { + dev_err(dev, "Error getting 4th out-masks for %s\n", + pout->name); + return res; + } + pout++; + } + return 0; +} + +void sync_all_pins(struct brcmstb_usb_pinmap_data *pdata) +{ + struct out_pin *pout; + struct in_pin *pin; + int val; + int x; + + /* + * Enable the override, clear any changed condition and + * propagate the state to the GPIO for all out pins. + */ + pout = pdata->out_pins; + for (x = 0; x < pdata->out_count; x++) { + pinmap_set(pdata->regs, pout->enable_mask); + pinmap_set(pdata->regs, pout->clr_changed_mask); + pinmap_unset(pdata->regs, pout->clr_changed_mask); + val = readl(pdata->regs) & pout->value_mask; + gpiod_set_value(pout->gpiod, val ? 1 : 0); + pout++; + } + + /* sync and enable all in pins. */ + pin = pdata->in_pins; + for (x = 0; x < pdata->in_count; x++) { + sync_in_pin(pin); + pinmap_set(pdata->regs, pin->enable_mask); + pin++; + } +} + +static int __init brcmstb_usb_pinmap_probe(struct platform_device *pdev) +{ + struct device_node *dn = pdev->dev.of_node; + struct brcmstb_usb_pinmap_data *pdata; + struct in_pin *pin; + struct resource *r; + int out_count; + int in_count; + int err; + int irq; + int x; + + get_pin_counts(dn, &in_count, &out_count); + if ((in_count + out_count) == 0) + return -EINVAL; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + pdata = devm_kzalloc(&pdev->dev, + sizeof(*pdata) + + (sizeof(struct in_pin) * in_count) + + (sizeof(struct out_pin) * out_count), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->in_count = in_count; + pdata->out_count = out_count; + pdata->in_pins = (struct in_pin *)(pdata + 1); + pdata->out_pins = (struct out_pin *)(pdata->in_pins + in_count); + + pdata->regs = devm_ioremap(&pdev->dev, r->start, resource_size(r)); + if (IS_ERR(pdata->regs)) + return PTR_ERR(pdata->regs); + platform_set_drvdata(pdev, pdata); + + err = parse_pins(&pdev->dev, dn, pdata); + if (err) + return err; + + sync_all_pins(pdata); + + if (out_count) { + + /* Enable interrupt for out pins */ + irq = platform_get_irq(pdev, 0); + err = devm_request_irq(&pdev->dev, irq, + brcmstb_usb_pinmap_ovr_isr, + IRQF_TRIGGER_RISING, + pdev->name, pdata); + if (err < 0) { + dev_err(&pdev->dev, "Error requesting IRQ\n"); + return err; + } + } + + for (x = 0, pin = pdata->in_pins; x < pdata->in_count; x++, pin++) { + irq = gpiod_to_irq(pin->gpiod); + if (irq < 0) { + dev_err(&pdev->dev, "Error getting IRQ for %s pin\n", + pin->name); + return irq; + } + err = devm_request_irq(&pdev->dev, irq, + brcmstb_usb_pinmap_gpio_isr, + IRQF_SHARED | IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + pdev->name, pin); + if (err < 0) { + dev_err(&pdev->dev, "Error requesting IRQ for %s pin\n", + pin->name); + return err; + } + } + + dev_dbg(&pdev->dev, "Driver probe succeeded\n"); + dev_dbg(&pdev->dev, "In pin count: %d, out pin count: %d\n", + pdata->in_count, pdata->out_count); + return 0; +} + + +static const struct of_device_id brcmstb_usb_pinmap_of_match[] = { + { .compatible = "brcm,usb-pinmap" }, + { }, +}; + +static struct platform_driver brcmstb_usb_pinmap_driver = { + .driver = { + .name = "brcm-usb-pinmap", + .of_match_table = brcmstb_usb_pinmap_of_match, + }, +}; + +static int __init brcmstb_usb_pinmap_init(void) +{ + return platform_driver_probe(&brcmstb_usb_pinmap_driver, + brcmstb_usb_pinmap_probe); +} + +module_init(brcmstb_usb_pinmap_init); +MODULE_AUTHOR("Al Cooper "); +MODULE_DESCRIPTION("Broadcom USB Pinmap Driver"); +MODULE_LICENSE("GPL"); From 862ee699fefe1e6d6f2c1518395f0b999b8beb15 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Mon, 19 Oct 2020 12:06:30 +0200 Subject: [PATCH 009/172] USB: sisusbvga: Make console support depend on BROKEN The console part of sisusbvga is broken vs. printk(). It uses in_atomic() to detect contexts in which it cannot sleep despite the big fat comment in preempt.h which says: Do not use in_atomic() in driver code. in_atomic() does not work on kernels with CONFIG_PREEMPT_COUNT=n which means that spin/rw_lock held regions are not detected by it. There is no way to make this work by handing context information through to the driver and this only can be solved once the core printk infrastructure supports sleepable console drivers. Make it depend on BROKEN for now. Fixes: 1bbb4f2035d9 ("[PATCH] USB: sisusb[vga] update") Signed-off-by: Thomas Gleixner Cc: Thomas Winischhofer Cc: Greg Kroah-Hartman Cc: linux-usb@vger.kernel.org Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20201019101109.603244207@linutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/sisusbvga/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/misc/sisusbvga/Kconfig b/drivers/usb/misc/sisusbvga/Kconfig index 655d9cb0651a..c12cdd015410 100644 --- a/drivers/usb/misc/sisusbvga/Kconfig +++ b/drivers/usb/misc/sisusbvga/Kconfig @@ -16,7 +16,7 @@ config USB_SISUSBVGA config USB_SISUSBVGA_CON bool "Text console and mode switching support" if USB_SISUSBVGA - depends on VT + depends on VT && BROKEN select FONT_8x16 help Say Y here if you want a VGA text console via the USB dongle or From 726c8277bc5e67799f2ac3ca7fc795d4bf07264f Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Mon, 19 Oct 2020 12:06:42 +0200 Subject: [PATCH 010/172] usb: atm: Replace in_interrupt() usage in comment in_interrupt() is a pretty vague context description as it means: hard interrupt, soft interrupt or bottom half disabled regions. Replace the vague comment with a proper reasoning why spin_lock_irqsave() needs to be used. Signed-off-by: Ahmed S. Darwish Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Signed-off-by: Duncan Sands Cc: linux-usb@vger.kernel.org Link: https://lore.kernel.org/r/20201019101110.944939915@linutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/atm/usbatm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/atm/usbatm.c b/drivers/usb/atm/usbatm.c index 56fe30d247da..f49792f951ab 100644 --- a/drivers/usb/atm/usbatm.c +++ b/drivers/usb/atm/usbatm.c @@ -249,7 +249,7 @@ static void usbatm_complete(struct urb *urb) /* vdbg("%s: urb 0x%p, status %d, actual_length %d", __func__, urb, status, urb->actual_length); */ - /* usually in_interrupt(), but not always */ + /* Can be invoked from task context, protect against interrupts */ spin_lock_irqsave(&channel->lock, flags); /* must add to the back when receiving; doesn't matter when sending */ From 19220bac2c134f25ce06e77da746793411536af6 Mon Sep 17 00:00:00 2001 From: "Ahmed S. Darwish" Date: Mon, 19 Oct 2020 12:06:37 +0200 Subject: [PATCH 011/172] usb: hosts: Remove in_interrupt() from comments The usage of in_interrupt() in drivers is phased out for various reasons. Various comments use !in_interrupt() to describe calling context for probe() and remove() functions. That's wrong because the calling context has to be preemptible task context, which is not what !in_interrupt() describes. Cleanup the comments. While at it add the missing kernel doc argument descriptors. Signed-off-by: Ahmed S. Darwish Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Acked-by: Krzysztof Kozlowski Acked-by: Alan Stern Cc: linux-usb@vger.kernel.org Cc: linux-omap@vger.kernel.org Cc: Kukjin Kim Cc: linux-arm-kernel@lists.infradead.org Cc: linux-samsung-soc@vger.kernel.org Link: https://lore.kernel.org/r/20201019101110.439968251@linutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-fsl.c | 9 ++++----- drivers/usb/host/ehci-pmcmsp.c | 11 +++++++---- drivers/usb/host/ohci-at91.c | 11 ++++++++--- drivers/usb/host/ohci-omap.c | 9 ++++++--- drivers/usb/host/ohci-pxa27x.c | 11 ++++++----- drivers/usb/host/ohci-s3c2410.c | 12 ++++++------ 6 files changed, 37 insertions(+), 26 deletions(-) diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c index 1e8b59ab2272..6f7bd6641694 100644 --- a/drivers/usb/host/ehci-fsl.c +++ b/drivers/usb/host/ehci-fsl.c @@ -39,10 +39,10 @@ static struct hc_driver __read_mostly fsl_ehci_hc_driver; /* * fsl_ehci_drv_probe - initialize FSL-based HCDs * @pdev: USB Host Controller being probed - * Context: !in_interrupt() + * + * Context: task context, might sleep * * Allocates basic resources for this USB host controller. - * */ static int fsl_ehci_drv_probe(struct platform_device *pdev) { @@ -684,12 +684,11 @@ static const struct ehci_driver_overrides ehci_fsl_overrides __initconst = { /** * fsl_ehci_drv_remove - shutdown processing for FSL-based HCDs * @pdev: USB Host Controller being removed - * Context: !in_interrupt() + * + * Context: task context, might sleep * * Reverses the effect of usb_hcd_fsl_probe(). - * */ - static int fsl_ehci_drv_remove(struct platform_device *pdev) { struct fsl_usb2_platform_data *pdata = dev_get_platdata(&pdev->dev); diff --git a/drivers/usb/host/ehci-pmcmsp.c b/drivers/usb/host/ehci-pmcmsp.c index 2d462fbbe0a6..0a3cc36c3598 100644 --- a/drivers/usb/host/ehci-pmcmsp.c +++ b/drivers/usb/host/ehci-pmcmsp.c @@ -147,12 +147,14 @@ err1: /** * usb_hcd_msp_probe - initialize PMC MSP-based HCDs - * Context: !in_interrupt() + * @driver: Pointer to hc driver instance + * @dev: USB controller to probe + * + * Context: task context, might sleep * * Allocates basic resources for this USB host controller, and * then invokes the start() method for the HCD associated with it * through the hotplug entry's driver_data. - * */ int usb_hcd_msp_probe(const struct hc_driver *driver, struct platform_device *dev) @@ -223,8 +225,9 @@ err1: /** * usb_hcd_msp_remove - shutdown processing for PMC MSP-based HCDs - * @dev: USB Host Controller being removed - * Context: !in_interrupt() + * @hcd: USB Host Controller being removed + * + * Context: task context, might sleep * * Reverses the effect of usb_hcd_msp_probe(), first invoking * the HCD's stop() method. It is always called from a thread diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index 0487a4b0501e..9bbd7ddd0003 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -155,7 +155,10 @@ static struct regmap *at91_dt_syscon_sfr(void) /* * usb_hcd_at91_probe - initialize AT91-based HCDs - * Context: !in_interrupt() + * @driver: Pointer to hc driver instance + * @pdev: USB controller to probe + * + * Context: task context, might sleep * * Allocates basic resources for this USB host controller, and * then invokes the start() method for the HCD associated with it @@ -246,12 +249,14 @@ static int usb_hcd_at91_probe(const struct hc_driver *driver, /* * usb_hcd_at91_remove - shutdown processing for AT91-based HCDs - * Context: !in_interrupt() + * @hcd: USB controller to remove + * @pdev: Platform device required for cleanup + * + * Context: task context, might sleep * * Reverses the effect of usb_hcd_at91_probe(), first invoking * the HCD's stop() method. It is always called from a thread * context, "rmmod" or something similar. - * */ static void usb_hcd_at91_remove(struct usb_hcd *hcd, struct platform_device *pdev) diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c index 9ccdf2c216b5..371959455b50 100644 --- a/drivers/usb/host/ohci-omap.c +++ b/drivers/usb/host/ohci-omap.c @@ -285,7 +285,9 @@ static int ohci_omap_reset(struct usb_hcd *hcd) /** * ohci_hcd_omap_probe - initialize OMAP-based HCDs - * Context: !in_interrupt() + * @pdev: USB controller to probe + * + * Context: task context, might sleep * * Allocates basic resources for this USB host controller, and * then invokes the start() method for the HCD associated with it @@ -399,8 +401,9 @@ err_put_hcd: /** * ohci_hcd_omap_remove - shutdown processing for OMAP-based HCDs - * @dev: USB Host Controller being removed - * Context: !in_interrupt() + * @pdev: USB Host Controller being removed + * + * Context: task context, might sleep * * Reverses the effect of ohci_hcd_omap_probe(), first invoking * the HCD's stop() method. It is always called from a thread diff --git a/drivers/usb/host/ohci-pxa27x.c b/drivers/usb/host/ohci-pxa27x.c index 7679fb583e41..54aa5c77e549 100644 --- a/drivers/usb/host/ohci-pxa27x.c +++ b/drivers/usb/host/ohci-pxa27x.c @@ -410,12 +410,13 @@ static int ohci_pxa_of_init(struct platform_device *pdev) /** * ohci_hcd_pxa27x_probe - initialize pxa27x-based HCDs - * Context: !in_interrupt() + * @pdev: USB Host controller to probe + * + * Context: task context, might sleep * * Allocates basic resources for this USB host controller, and * then invokes the start() method for the HCD associated with it * through the hotplug entry's driver_data. - * */ static int ohci_hcd_pxa27x_probe(struct platform_device *pdev) { @@ -509,13 +510,13 @@ static int ohci_hcd_pxa27x_probe(struct platform_device *pdev) /** * ohci_hcd_pxa27x_remove - shutdown processing for pxa27x-based HCDs - * @dev: USB Host Controller being removed - * Context: !in_interrupt() + * @pdev: USB Host Controller being removed + * + * Context: task context, might sleep * * Reverses the effect of ohci_hcd_pxa27x_probe(), first invoking * the HCD's stop() method. It is always called from a thread * context, normally "rmmod", "apmd", or something similar. - * */ static int ohci_hcd_pxa27x_remove(struct platform_device *pdev) { diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c index de5e570c58ba..1bec9b585e2d 100644 --- a/drivers/usb/host/ohci-s3c2410.c +++ b/drivers/usb/host/ohci-s3c2410.c @@ -324,14 +324,13 @@ static void s3c2410_hcd_oc(struct s3c2410_hcd_info *info, int port_oc) /* * ohci_hcd_s3c2410_remove - shutdown processing for HCD * @dev: USB Host Controller being removed - * Context: !in_interrupt() + * + * Context: task context, might sleep * * Reverses the effect of ohci_hcd_3c2410_probe(), first invoking * the HCD's stop() method. It is always called from a thread * context, normally "rmmod", "apmd", or something similar. - * -*/ - + */ static int ohci_hcd_s3c2410_remove(struct platform_device *dev) { @@ -345,12 +344,13 @@ ohci_hcd_s3c2410_remove(struct platform_device *dev) /* * ohci_hcd_s3c2410_probe - initialize S3C2410-based HCDs - * Context: !in_interrupt() + * @dev: USB Host Controller to be probed + * + * Context: task context, might sleep * * Allocates basic resources for this USB host controller, and * then invokes the start() method for the HCD associated with it * through the hotplug entry's driver_data. - * */ static int ohci_hcd_s3c2410_probe(struct platform_device *dev) { From 2e7e9b64be4356d9c67538cc88bff16a7f8850bb Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Mon, 19 Oct 2020 12:06:38 +0200 Subject: [PATCH 012/172] USB: host: ehci-pmcmsp: Cleanup usb_hcd_msp_remove() usb_hcd_msp_remove() has a pdev argument which isn't used and the function is used only within this file. Remove pdev and make usb_hcd_msp_remove() static. Signed-off-by: Thomas Gleixner Acked-by: Alan Stern Link: https://lore.kernel.org/r/20201019101110.530302737@linutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-pmcmsp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/ehci-pmcmsp.c b/drivers/usb/host/ehci-pmcmsp.c index 0a3cc36c3598..5fb92b956cc7 100644 --- a/drivers/usb/host/ehci-pmcmsp.c +++ b/drivers/usb/host/ehci-pmcmsp.c @@ -236,7 +236,7 @@ err1: * may be called without controller electrically present * may be called with controller, bus, and devices active */ -void usb_hcd_msp_remove(struct usb_hcd *hcd, struct platform_device *dev) +static void usb_hcd_msp_remove(struct usb_hcd *hcd) { usb_remove_hcd(hcd); iounmap(hcd->regs); @@ -309,7 +309,7 @@ static int ehci_hcd_msp_drv_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); - usb_hcd_msp_remove(hcd, pdev); + usb_hcd_msp_remove(hcd); /* free TWI GPIO USB_HOST_DEV pin */ gpio_free(MSP_PIN_USB0_HOST_DEV); From cce866155b5b96be83fa5ce8c7a56a1b0dd86cac Mon Sep 17 00:00:00 2001 From: "Ahmed S. Darwish" Date: Mon, 19 Oct 2020 12:06:40 +0200 Subject: [PATCH 013/172] usb: gadget: udc: Remove in_interrupt()/in_irq() from comments The usage of in_irq()/in_interrupt() in drivers is phased out for various reasons. The context description for usb_gadget_giveback_request() is misleading as in_interupt() means: hard interrupt or soft interrupt or bottom half disabled regions. But it's also invoked from task context when endpoints are torn down. Remove it as it's more confusing than helpful. Replace also the in_irq() comment with plain text. Signed-off-by: Ahmed S. Darwish Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Acked-by: Alan Stern Cc: Felipe Balbi Cc: Greg Kroah-Hartman Cc: linux-usb@vger.kernel.org Link: https://lore.kernel.org/r/20201019101110.744172050@linutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/core.c | 2 -- drivers/usb/gadget/udc/dummy_hcd.c | 6 ++++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index debf54205d22..5b5cfeb6c14a 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -897,8 +897,6 @@ EXPORT_SYMBOL_GPL(usb_gadget_unmap_request); * @ep: the endpoint to be used with with the request * @req: the request being given back * - * Context: in_interrupt() - * * This is called by device controller drivers in order to return the * completed request back to the gadget layer. */ diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index 53a227217f1c..1dde016ca86f 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -1754,8 +1754,10 @@ static int handle_control_request(struct dummy_hcd *dum_hcd, struct urb *urb, return ret_val; } -/* drive both sides of the transfers; looks like irq handlers to - * both drivers except the callbacks aren't in_irq(). +/* + * Drive both sides of the transfers; looks like irq handlers to both + * drivers except that the callbacks are invoked from soft interrupt + * context. */ static void dummy_timer(struct timer_list *t) { From 41631d3616c36305fef7c0e2e6412538a915dc97 Mon Sep 17 00:00:00 2001 From: "Ahmed S. Darwish" Date: Mon, 19 Oct 2020 12:06:41 +0200 Subject: [PATCH 014/172] usb: core: Replace in_interrupt() in comments The usage of in_interrupt() in drivers is phased out for various reasons. Various comments use !in_interrupt() to describe calling context for functions which might sleep. That's wrong because the calling context has to be preemptible task context, which is not what !in_interrupt() describes. Replace !in_interrupt() with more accurate plain text descriptions. The comment for usb_hcd_poll_rh_status() is misleading as this function is called from all kinds of contexts including preemptible task context. Remove it as there is obviously no restriction. Signed-off-by: Ahmed S. Darwish Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Acked-by: Alan Stern Cc: linux-usb@vger.kernel.org Link: https://lore.kernel.org/r/20201019101110.851821025@linutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/buffer.c | 6 ++++-- drivers/usb/core/hcd-pci.c | 6 ++++-- drivers/usb/core/hcd.c | 26 +++++++++++++++++--------- drivers/usb/core/hub.c | 3 ++- drivers/usb/core/message.c | 35 ++++++++++++++++++++++------------- drivers/usb/core/usb.c | 4 ++-- 6 files changed, 51 insertions(+), 29 deletions(-) diff --git a/drivers/usb/core/buffer.c b/drivers/usb/core/buffer.c index 6cf22c27f2d2..fbb087b728dc 100644 --- a/drivers/usb/core/buffer.c +++ b/drivers/usb/core/buffer.c @@ -51,7 +51,8 @@ void __init usb_init_pool_max(void) /** * hcd_buffer_create - initialize buffer pools * @hcd: the bus whose buffer pools are to be initialized - * Context: !in_interrupt() + * + * Context: task context, might sleep * * Call this as part of initializing a host controller that uses the dma * memory allocators. It initializes some pools of dma-coherent memory that @@ -88,7 +89,8 @@ int hcd_buffer_create(struct usb_hcd *hcd) /** * hcd_buffer_destroy - deallocate buffer pools * @hcd: the bus whose buffer pools are to be destroyed - * Context: !in_interrupt() + * + * Context: task context, might sleep * * This frees the buffer pools created by hcd_buffer_create(). */ diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index ec0d6c50610c..d630cccd2e6e 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -160,7 +160,8 @@ static void ehci_wait_for_companions(struct pci_dev *pdev, struct usb_hcd *hcd, * @dev: USB Host Controller being probed * @id: pci hotplug id connecting controller to HCD framework * @driver: USB HC driver handle - * Context: !in_interrupt() + * + * Context: task context, might sleep * * Allocates basic PCI resources for this USB host controller, and * then invokes the start() method for the HCD associated with it @@ -304,7 +305,8 @@ EXPORT_SYMBOL_GPL(usb_hcd_pci_probe); /** * usb_hcd_pci_remove - shutdown processing for PCI-based HCDs * @dev: USB Host Controller being removed - * Context: !in_interrupt() + * + * Context: task context, might sleep * * Reverses the effect of usb_hcd_pci_probe(), first invoking * the HCD's stop() method. It is always called from a thread diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 2c6b9578a7d3..90ce5841d9f5 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -747,8 +747,7 @@ error: * driver requests it; otherwise the driver is responsible for * calling usb_hcd_poll_rh_status() when an event occurs. * - * Completions are called in_interrupt(), but they may or may not - * be in_irq(). + * Completion handler may not sleep. See usb_hcd_giveback_urb() for details. */ void usb_hcd_poll_rh_status(struct usb_hcd *hcd) { @@ -904,7 +903,8 @@ static void usb_bus_init (struct usb_bus *bus) /** * usb_register_bus - registers the USB host controller with the usb core * @bus: pointer to the bus to register - * Context: !in_interrupt() + * + * Context: task context, might sleep. * * Assigns a bus number, and links the controller into usbcore data * structures so that it can be seen by scanning the bus list. @@ -939,7 +939,8 @@ error_find_busnum: /** * usb_deregister_bus - deregisters the USB host controller * @bus: pointer to the bus to deregister - * Context: !in_interrupt() + * + * Context: task context, might sleep. * * Recycles the bus number, and unlinks the controller from usbcore data * structures so that it won't be seen by scanning the bus list. @@ -1691,7 +1692,11 @@ static void usb_giveback_urb_bh(struct tasklet_struct *t) * @hcd: host controller returning the URB * @urb: urb being returned to the USB device driver. * @status: completion status code for the URB. - * Context: in_interrupt() + * + * Context: atomic. The completion callback is invoked in caller's context. + * For HCDs with HCD_BH flag set, the completion callback is invoked in tasklet + * context (except for URBs submitted to the root hub which always complete in + * caller's context). * * This hands the URB from HCD to its USB device driver, using its * completion function. The HCD has freed all per-urb resources @@ -2268,7 +2273,7 @@ EXPORT_SYMBOL_GPL(usb_hcd_resume_root_hub); * usb_bus_start_enum - start immediate enumeration (for OTG) * @bus: the bus (must use hcd framework) * @port_num: 1-based number of port; usually bus->otg_port - * Context: in_interrupt() + * Context: atomic * * Starts enumeration, with an immediate reset followed later by * hub_wq identifying and possibly configuring the device. @@ -2474,7 +2479,8 @@ EXPORT_SYMBOL_GPL(__usb_create_hcd); * @bus_name: value to store in hcd->self.bus_name * @primary_hcd: a pointer to the usb_hcd structure that is sharing the * PCI device. Only allocate certain resources for the primary HCD - * Context: !in_interrupt() + * + * Context: task context, might sleep. * * Allocate a struct usb_hcd, with extra space at the end for the * HC driver's private data. Initialize the generic members of the @@ -2496,7 +2502,8 @@ EXPORT_SYMBOL_GPL(usb_create_shared_hcd); * @driver: HC driver that will use this hcd * @dev: device for this HC, stored in hcd->self.controller * @bus_name: value to store in hcd->self.bus_name - * Context: !in_interrupt() + * + * Context: task context, might sleep. * * Allocate a struct usb_hcd, with extra space at the end for the * HC driver's private data. Initialize the generic members of the @@ -2830,7 +2837,8 @@ EXPORT_SYMBOL_GPL(usb_add_hcd); /** * usb_remove_hcd - shutdown processing for generic HCDs * @hcd: the usb_hcd structure to remove - * Context: !in_interrupt() + * + * Context: task context, might sleep. * * Disconnects the root hub, then reverses the effects of usb_add_hcd(), * invoking the HCD's stop() method. diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 17202b2ee063..7f71218cc1e5 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2171,7 +2171,8 @@ static void hub_disconnect_children(struct usb_device *udev) /** * usb_disconnect - disconnect a device (usbcore-internal) * @pdev: pointer to device being disconnected - * Context: !in_interrupt () + * + * Context: task context, might sleep * * Something got disconnected. Get rid of it and all of its children. * diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 19ebb542befc..c4e876050074 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -119,7 +119,7 @@ static int usb_internal_control_msg(struct usb_device *usb_dev, * @timeout: time in msecs to wait for the message to complete before timing * out (if 0 the wait is forever) * - * Context: !in_interrupt () + * Context: task context, might sleep. * * This function sends a simple control message to a specified endpoint and * waits for the message to complete, or timeout. @@ -310,7 +310,7 @@ EXPORT_SYMBOL_GPL(usb_control_msg_recv); * @timeout: time in msecs to wait for the message to complete before * timing out (if 0 the wait is forever) * - * Context: !in_interrupt () + * Context: task context, might sleep. * * This function sends a simple interrupt message to a specified endpoint and * waits for the message to complete, or timeout. @@ -343,7 +343,7 @@ EXPORT_SYMBOL_GPL(usb_interrupt_msg); * @timeout: time in msecs to wait for the message to complete before * timing out (if 0 the wait is forever) * - * Context: !in_interrupt () + * Context: task context, might sleep. * * This function sends a simple bulk message to a specified endpoint * and waits for the message to complete, or timeout. @@ -610,7 +610,8 @@ EXPORT_SYMBOL_GPL(usb_sg_init); * usb_sg_wait - synchronously execute scatter/gather request * @io: request block handle, as initialized with usb_sg_init(). * some fields become accessible when this call returns. - * Context: !in_interrupt () + * + * Context: task context, might sleep. * * This function blocks until the specified I/O operation completes. It * leverages the grouping of the related I/O requests to get good transfer @@ -764,7 +765,8 @@ EXPORT_SYMBOL_GPL(usb_sg_cancel); * @index: the number of the descriptor * @buf: where to put the descriptor * @size: how big is "buf"? - * Context: !in_interrupt () + * + * Context: task context, might sleep. * * Gets a USB descriptor. Convenience functions exist to simplify * getting some types of descriptors. Use @@ -812,7 +814,8 @@ EXPORT_SYMBOL_GPL(usb_get_descriptor); * @index: the number of the descriptor * @buf: where to put the string * @size: how big is "buf"? - * Context: !in_interrupt () + * + * Context: task context, might sleep. * * Retrieves a string, encoded using UTF-16LE (Unicode, 16 bits per character, * in little-endian byte order). @@ -947,7 +950,8 @@ static int usb_get_langid(struct usb_device *dev, unsigned char *tbuf) * @index: the number of the descriptor * @buf: where to put the string * @size: how big is "buf"? - * Context: !in_interrupt () + * + * Context: task context, might sleep. * * This converts the UTF-16LE encoded strings returned by devices, from * usb_get_string_descriptor(), to null-terminated UTF-8 encoded ones @@ -1036,7 +1040,8 @@ char *usb_cache_string(struct usb_device *udev, int index) * usb_get_device_descriptor - (re)reads the device descriptor (usbcore) * @dev: the device whose device descriptor is being updated * @size: how much of the descriptor to read - * Context: !in_interrupt () + * + * Context: task context, might sleep. * * Updates the copy of the device descriptor stored in the device structure, * which dedicates space for this purpose. @@ -1071,7 +1076,7 @@ int usb_get_device_descriptor(struct usb_device *dev, unsigned int size) /* * usb_set_isoch_delay - informs the device of the packet transmit delay * @dev: the device whose delay is to be informed - * Context: !in_interrupt() + * Context: task context, might sleep * * Since this is an optional request, we don't bother if it fails. */ @@ -1100,7 +1105,8 @@ int usb_set_isoch_delay(struct usb_device *dev) * @type: USB_STATUS_TYPE_*; for standard or PTM status types * @target: zero (for device), else interface or endpoint number * @data: pointer to two bytes of bitmap data - * Context: !in_interrupt () + * + * Context: task context, might sleep. * * Returns device, interface, or endpoint status. Normally only of * interest to see if the device is self powered, or has enabled the @@ -1177,7 +1183,8 @@ EXPORT_SYMBOL_GPL(usb_get_status); * usb_clear_halt - tells device to clear endpoint halt/stall condition * @dev: device whose endpoint is halted * @pipe: endpoint "pipe" being cleared - * Context: !in_interrupt () + * + * Context: task context, might sleep. * * This is used to clear halt conditions for bulk and interrupt endpoints, * as reported by URB completion status. Endpoints that are halted are @@ -1481,7 +1488,8 @@ void usb_enable_interface(struct usb_device *dev, * @dev: the device whose interface is being updated * @interface: the interface being updated * @alternate: the setting being chosen. - * Context: !in_interrupt () + * + * Context: task context, might sleep. * * This is used to enable data transfers on interfaces that may not * be enabled by default. Not all devices support such configurability. @@ -1902,7 +1910,8 @@ static void __usb_queue_reset_device(struct work_struct *ws) * usb_set_configuration - Makes a particular device setting be current * @dev: the device whose configuration is being updated * @configuration: the configuration being chosen. - * Context: !in_interrupt(), caller owns the device lock + * + * Context: task context, might sleep. Caller holds device lock. * * This is used to enable non-default device modes. Not all devices * use this kind of configurability; many devices only have one diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 9b4ac4415f1a..8f07b0516100 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -28,7 +28,6 @@ #include #include #include -#include /* for in_interrupt() */ #include #include #include @@ -561,7 +560,8 @@ static bool usb_dev_authorized(struct usb_device *dev, struct usb_hcd *hcd) * @parent: hub to which device is connected; null to allocate a root hub * @bus: bus used to access the device * @port1: one-based index of port; ignored for root hubs - * Context: !in_interrupt() + * + * Context: task context, might sleep. * * Only hub drivers (including virtual root hub drivers for host * controllers) should ever call this. From b02dfc13fa35d2d514dc24261b1cf2589d369852 Mon Sep 17 00:00:00 2001 From: "Ahmed S. Darwish" Date: Mon, 19 Oct 2020 12:06:39 +0200 Subject: [PATCH 015/172] usb: gadget: pxa27x_udc: Replace in_interrupt() usage in comments The usage of in_interrupt() in drivers is phased out for various reasons. Documenting calling contexts of functions with 'in_interrupt()' or '!in_interrupt()' is imprecise: For a function which might sleep the condition is preemptible task context, which is not what '!in_interrupt()' describes. Replace the context docummentation with plain text and make them match reality. Signed-off-by: Ahmed S. Darwish Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Cc: Felipe Balbi Cc: Greg Kroah-Hartman Cc: linux-arm-kernel@lists.infradead.org Cc: linux-usb@vger.kernel.org Link: https://lore.kernel.org/r/20201019101110.636378243@linutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/pxa27x_udc.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/usb/gadget/udc/pxa27x_udc.c b/drivers/usb/gadget/udc/pxa27x_udc.c index cfaeca457fa7..ce57961dfd2d 100644 --- a/drivers/usb/gadget/udc/pxa27x_udc.c +++ b/drivers/usb/gadget/udc/pxa27x_udc.c @@ -304,7 +304,7 @@ static struct pxa_ep *find_pxa_ep(struct pxa_udc *udc, * update_pxa_ep_matches - update pxa_ep cached values in all udc_usb_ep * @udc: pxa udc * - * Context: in_interrupt() + * Context: interrupt handler * * Updates all pxa_ep fields in udc_usb_ep structures, if this field was * previously set up (and is not NULL). The update is necessary is a @@ -859,7 +859,7 @@ static int write_packet(struct pxa_ep *ep, struct pxa27x_request *req, * @ep: pxa physical endpoint * @req: usb request * - * Context: callable when in_interrupt() + * Context: interrupt handler * * Unload as many packets as possible from the fifo we use for usb OUT * transfers and put them into the request. Caller should have made sure @@ -997,7 +997,7 @@ static int read_ep0_fifo(struct pxa_ep *ep, struct pxa27x_request *req) * @ep: control endpoint * @req: request * - * Context: callable when in_interrupt() + * Context: interrupt handler * * Sends a request (or a part of the request) to the control endpoint (ep0 in). * If the request doesn't fit, the remaining part will be sent from irq. @@ -1036,8 +1036,8 @@ static int write_ep0_fifo(struct pxa_ep *ep, struct pxa27x_request *req) * @_req: usb request * @gfp_flags: flags * - * Context: normally called when !in_interrupt, but callable when in_interrupt() - * in the special case of ep0 setup : + * Context: thread context or from the interrupt handler in the + * special case of ep0 setup : * (irq->handle_ep0_ctrl_req->gadget_setup->pxa_ep_queue) * * Returns 0 if succedeed, error otherwise @@ -1512,7 +1512,8 @@ static int should_disable_udc(struct pxa_udc *udc) * pxa_udc_pullup - Offer manual D+ pullup control * @_gadget: usb gadget using the control * @is_active: 0 if disconnect, else connect D+ pullup resistor - * Context: !in_interrupt() + * + * Context: task context, might sleep * * Returns 0 if OK, -EOPNOTSUPP if udc driver doesn't handle D+ pullup */ @@ -1560,7 +1561,7 @@ static int pxa_udc_vbus_session(struct usb_gadget *_gadget, int is_active) * @_gadget: usb gadget * @mA: current drawn * - * Context: !in_interrupt() + * Context: task context, might sleep * * Called after a configuration was chosen by a USB host, to inform how much * current can be drawn by the device from VBus line. @@ -1886,7 +1887,7 @@ stall: * @fifo_irq: 1 if triggered by fifo service type irq * @opc_irq: 1 if triggered by output packet complete type irq * - * Context : when in_interrupt() or with ep->lock held + * Context : interrupt handler * * Tries to transfer all pending request data into the endpoint and/or * transfer all pending data in the endpoint into usb requests. @@ -2011,7 +2012,7 @@ static void handle_ep0(struct pxa_udc *udc, int fifo_irq, int opc_irq) * Tries to transfer all pending request data into the endpoint and/or * transfer all pending data in the endpoint into usb requests. * - * Is always called when in_interrupt() and with ep->lock released. + * Is always called from the interrupt handler. ep->lock must not be held. */ static void handle_ep(struct pxa_ep *ep) { From 9ca9a2525ddff9618f16a0b880ccff74ffa703cf Mon Sep 17 00:00:00 2001 From: "Ahmed S. Darwish" Date: Mon, 19 Oct 2020 12:06:36 +0200 Subject: [PATCH 016/172] usbip: Remove in_interrupt() check The usage of in_interrupt() in drivers is phased out and Linus clearly requested that code which changes behaviour depending on context should either be separated or the context be conveyed in an argument passed by the caller, which usually knows the context. usbip_recv() uses in_interrupt() to conditionally print context information for debugging messages. The value is zero as the function is only called from various *_rx_loop() kthread functions. Remove it. Signed-off-by: Ahmed S. Darwish Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Acked-by: Shuah Khan Cc: Valentina Manea Cc: Greg Kroah-Hartman Cc: linux-usb@vger.kernel.org Link: https://lore.kernel.org/r/20201019101110.332963099@linutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/usbip_common.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/usb/usbip/usbip_common.c b/drivers/usb/usbip/usbip_common.c index 4ce6c6a45eb1..2ab99244bc31 100644 --- a/drivers/usb/usbip/usbip_common.c +++ b/drivers/usb/usbip/usbip_common.c @@ -324,11 +324,6 @@ int usbip_recv(struct socket *sock, void *buf, int size) } while (msg_data_left(&msg)); if (usbip_dbg_flag_xmit) { - if (!in_interrupt()) - pr_debug("%-10s:", current->comm); - else - pr_debug("interrupt :"); - pr_debug("receiving....\n"); usbip_dump_buffer(buf, size); pr_debug("received, osize %d ret %d size %zd total %d\n", From 96eea5876eb01c35f6eda225f0cdc752a59b6615 Mon Sep 17 00:00:00 2001 From: "Ahmed S. Darwish" Date: Mon, 19 Oct 2020 12:06:34 +0200 Subject: [PATCH 017/172] usb: xhci: Remove in_interrupt() checks The usage of in_interrupt() in drivers is phased out for various reasons. xhci_set_hc_event_deq() has an !in_interrupt() check which is pointless because the function is only invoked from xhci_mem_init() which is clearly task context as it does GFP_KERNEL allocations. Remove it. xhci_urb_enqueue() prints a debug message if an URB is submitted after the underlying hardware was suspended. But that warning is only issued when in_interrupt() is true, which makes no sense. Simply return -ESHUTDOWN and be done with it. Signed-off-by: Ahmed S. Darwish Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Thomas Gleixner Cc: Mathias Nyman Cc: Greg Kroah-Hartman Cc: linux-usb@vger.kernel.org Link: https://lore.kernel.org/r/20201019101110.148631116@linutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 2 +- drivers/usb/host/xhci.c | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index b46ef45c4d25..da305269693a 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2109,7 +2109,7 @@ static void xhci_set_hc_event_deq(struct xhci_hcd *xhci) deq = xhci_trb_virt_to_dma(xhci->event_ring->deq_seg, xhci->event_ring->dequeue); - if (deq == 0 && !in_interrupt()) + if (!deq) xhci_warn(xhci, "WARN something wrong with SW event ring " "dequeue ptr.\n"); /* Update HC event ring dequeue pointer */ diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 482fe8c5e3b4..8654c2ada046 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1476,11 +1476,9 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag ep_index = xhci_get_endpoint_index(&urb->ep->desc); ep_state = &xhci->devs[slot_id]->eps[ep_index].ep_state; - if (!HCD_HW_ACCESSIBLE(hcd)) { - if (!in_interrupt()) - xhci_dbg(xhci, "urb submitted during PCI suspend\n"); + if (!HCD_HW_ACCESSIBLE(hcd)) return -ESHUTDOWN; - } + if (xhci->devs[slot_id]->flags & VDEV_PORT_ERROR) { xhci_dbg(xhci, "Can't queue urb, port error, link inactive\n"); return -ENODEV; From 5ed132db5ad4f58156ae9d28219396b6f764a9cb Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Tue, 20 Oct 2020 02:36:18 -0700 Subject: [PATCH 018/172] dt-bindings: connector: Add property to set initial current cap for FRS This change adds frs-typec-current which allows setting the initial current capability of the new source when vSafe5V is applied during PD3.0 sink Fast Role Swap. Signed-off-by: Badhri Jagan Sridharan Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20201020093627.256885-2-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/connector/usb-connector.yaml | 19 +++++++++++++++++++ include/dt-bindings/usb/pd.h | 8 ++++++++ 2 files changed, 27 insertions(+) diff --git a/Documentation/devicetree/bindings/connector/usb-connector.yaml b/Documentation/devicetree/bindings/connector/usb-connector.yaml index 728f82db073d..62781518aefc 100644 --- a/Documentation/devicetree/bindings/connector/usb-connector.yaml +++ b/Documentation/devicetree/bindings/connector/usb-connector.yaml @@ -147,6 +147,25 @@ properties: required: - port@0 + new-source-frs-typec-current: + description: Initial current capability of the new source when vSafe5V + is applied during PD3.0 Fast Role Swap. "Table 6-14 Fixed Supply PDO - Sink" + of "USB Power Delivery Specification Revision 3.0, Version 1.2" provides the + different power levels and "6.4.1.3.1.6 Fast Role Swap USB Type-C Current" + provides a detailed description of the field. The sink PDO from current source + reflects the current source's(i.e. transmitter of the FRS signal) power + requirement during fr swap. The current sink (i.e. receiver of the FRS signal), + a.k.a new source, should check if it will be able to satisfy the current source's, + new sink's, requirement during frswap before enabling the frs signal reception. + This property refers to maximum current capability that the current sink can + satisfy. During FRS, VBUS voltage is at 5V, as the partners are in implicit + contract, hence, the power level is only a function of the current capability. + "1" refers to default USB power level as described by "Table 6-14 Fixed Supply PDO - Sink". + "2" refers to 1.5A@5V. + "3" refers to 3.0A@5V. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 2, 3] + required: - compatible diff --git a/include/dt-bindings/usb/pd.h b/include/dt-bindings/usb/pd.h index 985f2bbd4d24..0352893697f0 100644 --- a/include/dt-bindings/usb/pd.h +++ b/include/dt-bindings/usb/pd.h @@ -85,4 +85,12 @@ PDO_PPS_APDO_MIN_VOLT(min_mv) | PDO_PPS_APDO_MAX_VOLT(max_mv) | \ PDO_PPS_APDO_MAX_CURR(max_ma)) + /* + * Based on "Table 6-14 Fixed Supply PDO - Sink" of "USB Power Delivery Specification Revision 3.0, + * Version 1.2" + * Initial current capability of the new source when vSafe5V is applied. + */ +#define FRS_DEFAULT_POWER 1 +#define FRS_5V_1P5A 2 +#define FRS_5V_3A 3 #endif /* __DT_POWER_DELIVERY_H */ From ecbb4dac5f6ccbc5443609ec72021994b662e06d Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 28 Oct 2020 12:31:07 +0100 Subject: [PATCH 019/172] USB: host: isp1362: delete isp1362_show_regs() No one is calling this function, so it's pointless to keep around as it is triggering automated scanning tools to try to fix up the problems with it using in_interrupt(). So delete the thing. Reported-by: Thomas Gleixner Reported-by: Sebastian Andrzej Siewior Reported-by: Ahmed S. Darwish Reviewed-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20201028113107.2007742-1-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/isp1362.h | 54 -------------------------------------- 1 file changed, 54 deletions(-) diff --git a/drivers/usb/host/isp1362.h b/drivers/usb/host/isp1362.h index 9bbfcc3fdd3c..208705b08d37 100644 --- a/drivers/usb/host/isp1362.h +++ b/drivers/usb/host/isp1362.h @@ -793,60 +793,6 @@ static void isp1362_write_fifo(struct isp1362_hcd *isp1362_hcd, void *buf, u16 l ISP1362_REG_NO(ISP1362_REG_##r), isp1362_read_reg16(d, r)); \ } -static void __attribute__((__unused__)) isp1362_show_regs(struct isp1362_hcd *isp1362_hcd) -{ - isp1362_show_reg(isp1362_hcd, HCREVISION); - isp1362_show_reg(isp1362_hcd, HCCONTROL); - isp1362_show_reg(isp1362_hcd, HCCMDSTAT); - isp1362_show_reg(isp1362_hcd, HCINTSTAT); - isp1362_show_reg(isp1362_hcd, HCINTENB); - isp1362_show_reg(isp1362_hcd, HCFMINTVL); - isp1362_show_reg(isp1362_hcd, HCFMREM); - isp1362_show_reg(isp1362_hcd, HCFMNUM); - isp1362_show_reg(isp1362_hcd, HCLSTHRESH); - isp1362_show_reg(isp1362_hcd, HCRHDESCA); - isp1362_show_reg(isp1362_hcd, HCRHDESCB); - isp1362_show_reg(isp1362_hcd, HCRHSTATUS); - isp1362_show_reg(isp1362_hcd, HCRHPORT1); - isp1362_show_reg(isp1362_hcd, HCRHPORT2); - - isp1362_show_reg(isp1362_hcd, HCHWCFG); - isp1362_show_reg(isp1362_hcd, HCDMACFG); - isp1362_show_reg(isp1362_hcd, HCXFERCTR); - isp1362_show_reg(isp1362_hcd, HCuPINT); - - if (in_interrupt()) - DBG(0, "%-12s[%02x]: %04x\n", "HCuPINTENB", - ISP1362_REG_NO(ISP1362_REG_HCuPINTENB), isp1362_hcd->irqenb); - else - isp1362_show_reg(isp1362_hcd, HCuPINTENB); - isp1362_show_reg(isp1362_hcd, HCCHIPID); - isp1362_show_reg(isp1362_hcd, HCSCRATCH); - isp1362_show_reg(isp1362_hcd, HCBUFSTAT); - isp1362_show_reg(isp1362_hcd, HCDIRADDR); - /* Access would advance fifo - * isp1362_show_reg(isp1362_hcd, HCDIRDATA); - */ - isp1362_show_reg(isp1362_hcd, HCISTLBUFSZ); - isp1362_show_reg(isp1362_hcd, HCISTLRATE); - isp1362_show_reg(isp1362_hcd, HCINTLBUFSZ); - isp1362_show_reg(isp1362_hcd, HCINTLBLKSZ); - isp1362_show_reg(isp1362_hcd, HCINTLDONE); - isp1362_show_reg(isp1362_hcd, HCINTLSKIP); - isp1362_show_reg(isp1362_hcd, HCINTLLAST); - isp1362_show_reg(isp1362_hcd, HCINTLCURR); - isp1362_show_reg(isp1362_hcd, HCATLBUFSZ); - isp1362_show_reg(isp1362_hcd, HCATLBLKSZ); - /* only valid after ATL_DONE interrupt - * isp1362_show_reg(isp1362_hcd, HCATLDONE); - */ - isp1362_show_reg(isp1362_hcd, HCATLSKIP); - isp1362_show_reg(isp1362_hcd, HCATLLAST); - isp1362_show_reg(isp1362_hcd, HCATLCURR); - isp1362_show_reg(isp1362_hcd, HCATLDTC); - isp1362_show_reg(isp1362_hcd, HCATLDTCTO); -} - static void isp1362_write_diraddr(struct isp1362_hcd *isp1362_hcd, u16 offset, u16 len) { len = (len + 1) & ~1; From aee9ddb1d3718d3ba05b50c51622d7792ae749c9 Mon Sep 17 00:00:00 2001 From: Andrey Konovalov Date: Fri, 16 Oct 2020 15:57:45 +0200 Subject: [PATCH 020/172] kcov, usb: only collect coverage from __usb_hcd_giveback_urb in softirq Currently there's a KCOV remote coverage collection section in __usb_hcd_giveback_urb(). Initially that section was added based on the assumption that usb_hcd_giveback_urb() can only be called in interrupt context as indicated by a comment before it. This is what happens when syzkaller is fuzzing the USB stack via the dummy_hcd driver. As it turns out, it's actually valid to call usb_hcd_giveback_urb() in task context, provided that the caller turned off the interrupts; USB/IP does exactly that. This can lead to a nested KCOV remote coverage collection sections both trying to collect coverage in task context. This isn't supported by KCOV, and leads to a WARNING. Change __usb_hcd_giveback_urb() to only call kcov_remote_*() callbacks when it's being executed in a softirq. As the result, the coverage from USB/IP related usb_hcd_giveback_urb() calls won't be collected, but the WARNING is fixed. A potential future improvement would be to support nested remote coverage collection sections, but this patch doesn't address that. Reviewed-by: Dmitry Vyukov Acked-by: Marco Elver Signed-off-by: Andrey Konovalov Link: https://lore.kernel.org/r/f3a7a153f0719cb53ec385b16e912798bd3e4cf9.1602856358.git.andreyknvl@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 90ce5841d9f5..60886a7464c3 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1647,9 +1647,16 @@ static void __usb_hcd_giveback_urb(struct urb *urb) /* pass ownership to the completion handler */ urb->status = status; - kcov_remote_start_usb((u64)urb->dev->bus->busnum); + /* + * This function can be called in task context inside another remote + * coverage collection section, but KCOV doesn't support that kind of + * recursion yet. Only collect coverage in softirq context for now. + */ + if (in_serving_softirq()) + kcov_remote_start_usb((u64)urb->dev->bus->busnum); urb->complete(urb); - kcov_remote_stop(); + if (in_serving_softirq()) + kcov_remote_stop(); usb_anchor_resume_wakeups(anchor); atomic_dec(&urb->use_count); From 35ad0d901eac56ac6af2e4d726f255486c1ee36c Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 2 Nov 2020 10:56:55 +0300 Subject: [PATCH 021/172] usb: misc: brcmstb-usb-pinmap: Fix an IS_ERR() vs NULL check The devm_ioremap() function doesn't return error pointers, it returns NULL on error. Fixes: 517c4c44b323 ("usb: Add driver to allow any GPIO to be used for 7211 USB signals") Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/20201102075655.GA4163205@mwanda Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/brcmstb-usb-pinmap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/misc/brcmstb-usb-pinmap.c b/drivers/usb/misc/brcmstb-usb-pinmap.c index 02144c39aaba..2326e60545f7 100644 --- a/drivers/usb/misc/brcmstb-usb-pinmap.c +++ b/drivers/usb/misc/brcmstb-usb-pinmap.c @@ -277,8 +277,8 @@ static int __init brcmstb_usb_pinmap_probe(struct platform_device *pdev) pdata->out_pins = (struct out_pin *)(pdata->in_pins + in_count); pdata->regs = devm_ioremap(&pdev->dev, r->start, resource_size(r)); - if (IS_ERR(pdata->regs)) - return PTR_ERR(pdata->regs); + if (!pdata->regs) + return -ENOMEM; platform_set_drvdata(pdev, pdata); err = parse_pins(&pdev->dev, dn, pdata); From 696c541c8c6cfa05d65aa24ae2b9e720fc01766e Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Sun, 25 Oct 2020 18:45:47 +0100 Subject: [PATCH 022/172] USB: serial: keyspan_pda: fix dropped unthrottle interrupts Commit c528fcb116e6 ("USB: serial: keyspan_pda: fix receive sanity checks") broke write-unthrottle handling by dropping well-formed unthrottle-interrupt packets which are precisely two bytes long. This could lead to blocked writers not being woken up when buffer space again becomes available. Instead, stop unconditionally printing the third byte which is (presumably) only valid on modem-line changes. Fixes: c528fcb116e6 ("USB: serial: keyspan_pda: fix receive sanity checks") Cc: stable # 4.11 Acked-by: Sebastian Andrzej Siewior Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/keyspan_pda.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index c1333919716b..2d5ad579475a 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -172,11 +172,11 @@ static void keyspan_pda_rx_interrupt(struct urb *urb) break; case 1: /* status interrupt */ - if (len < 3) { + if (len < 2) { dev_warn(&port->dev, "short interrupt message received\n"); break; } - dev_dbg(&port->dev, "rx int, d1=%d, d2=%d\n", data[1], data[2]); + dev_dbg(&port->dev, "rx int, d1=%d\n", data[1]); switch (data[1]) { case 1: /* modemline change */ break; From 7353cad7ee4deaefc16e94727e69285563e219f6 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Sun, 25 Oct 2020 18:45:48 +0100 Subject: [PATCH 023/172] USB: serial: keyspan_pda: fix write deadlock The write() callback can be called in interrupt context (e.g. when used as a console) so interrupts must be disabled while holding the port lock to prevent a possible deadlock. Fixes: e81ee637e4ae ("usb-serial: possible irq lock inversion (PPP vs. usb/serial)") Fixes: 507ca9bc0476 ("[PATCH] USB: add ability for usb-serial drivers to determine if their write urb is currently being used.") Cc: stable # 2.6.19 Acked-by: Sebastian Andrzej Siewior Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/keyspan_pda.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index 2d5ad579475a..17b60e5a9f1f 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -443,6 +443,7 @@ static int keyspan_pda_write(struct tty_struct *tty, int request_unthrottle = 0; int rc = 0; struct keyspan_pda_private *priv; + unsigned long flags; priv = usb_get_serial_port_data(port); /* guess how much room is left in the device's ring buffer, and if we @@ -462,13 +463,13 @@ static int keyspan_pda_write(struct tty_struct *tty, the TX urb is in-flight (wait until it completes) the device is full (wait until it says there is room) */ - spin_lock_bh(&port->lock); + spin_lock_irqsave(&port->lock, flags); if (!test_bit(0, &port->write_urbs_free) || priv->tx_throttled) { - spin_unlock_bh(&port->lock); + spin_unlock_irqrestore(&port->lock, flags); return 0; } clear_bit(0, &port->write_urbs_free); - spin_unlock_bh(&port->lock); + spin_unlock_irqrestore(&port->lock, flags); /* At this point the URB is in our control, nobody else can submit it again (the only sudden transition was the one from EINPROGRESS to From c01d2c58698f710c9e13ba3e2d296328606f74fd Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Sun, 25 Oct 2020 18:45:49 +0100 Subject: [PATCH 024/172] USB: serial: keyspan_pda: fix stalled writes Make sure to clear the write-busy flag also in case no new data was submitted due to lack of device buffer space so that writing is resumed once space again becomes available. Fixes: 507ca9bc0476 ("[PATCH] USB: add ability for usb-serial drivers to determine if their write urb is currently being used.") Cc: stable # 2.6.13 Acked-by: Sebastian Andrzej Siewior Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/keyspan_pda.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index 17b60e5a9f1f..d6ebde779e85 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -548,7 +548,7 @@ static int keyspan_pda_write(struct tty_struct *tty, rc = count; exit: - if (rc < 0) + if (rc <= 0) set_bit(0, &port->write_urbs_free); return rc; } From 37faf50615412947868c49aee62f68233307f4e4 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Sun, 25 Oct 2020 18:45:50 +0100 Subject: [PATCH 025/172] USB: serial: keyspan_pda: fix write-wakeup use-after-free The driver's deferred write wakeup was never flushed on disconnect, something which could lead to the driver port data being freed while the wakeup work is still scheduled. Fix this by using the usb-serial write wakeup which gets cancelled properly on disconnect. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Acked-by: Sebastian Andrzej Siewior Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/keyspan_pda.c | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index d6ebde779e85..d91180ab5f3b 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -43,8 +43,7 @@ struct keyspan_pda_private { int tx_room; int tx_throttled; - struct work_struct wakeup_work; - struct work_struct unthrottle_work; + struct work_struct unthrottle_work; struct usb_serial *serial; struct usb_serial_port *port; }; @@ -97,15 +96,6 @@ static const struct usb_device_id id_table_fake_xircom[] = { }; #endif -static void keyspan_pda_wakeup_write(struct work_struct *work) -{ - struct keyspan_pda_private *priv = - container_of(work, struct keyspan_pda_private, wakeup_work); - struct usb_serial_port *port = priv->port; - - tty_port_tty_wakeup(&port->port); -} - static void keyspan_pda_request_unthrottle(struct work_struct *work) { struct keyspan_pda_private *priv = @@ -183,7 +173,7 @@ static void keyspan_pda_rx_interrupt(struct urb *urb) case 2: /* tx unthrottle interrupt */ priv->tx_throttled = 0; /* queue up a wakeup at scheduler time */ - schedule_work(&priv->wakeup_work); + usb_serial_port_softint(port); break; default: break; @@ -563,7 +553,7 @@ static void keyspan_pda_write_bulk_callback(struct urb *urb) priv = usb_get_serial_port_data(port); /* queue up a wakeup at scheduler time */ - schedule_work(&priv->wakeup_work); + usb_serial_port_softint(port); } @@ -715,7 +705,6 @@ static int keyspan_pda_port_probe(struct usb_serial_port *port) if (!priv) return -ENOMEM; - INIT_WORK(&priv->wakeup_work, keyspan_pda_wakeup_write); INIT_WORK(&priv->unthrottle_work, keyspan_pda_request_unthrottle); priv->serial = port->serial; priv->port = port; From 49fbb8e37a961396a5b6c82937c70df91de45e9d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Sun, 25 Oct 2020 18:45:51 +0100 Subject: [PATCH 026/172] USB: serial: keyspan_pda: fix tx-unthrottle use-after-free The driver's transmit-unthrottle work was never flushed on disconnect, something which could lead to the driver port data being freed while the unthrottle work is still scheduled. Fix this by cancelling the unthrottle work when shutting down the port. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Acked-by: Sebastian Andrzej Siewior Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/keyspan_pda.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index d91180ab5f3b..781b6723379f 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -647,8 +647,12 @@ error: } static void keyspan_pda_close(struct usb_serial_port *port) { + struct keyspan_pda_private *priv = usb_get_serial_port_data(port); + usb_kill_urb(port->write_urb); usb_kill_urb(port->interrupt_in_urb); + + cancel_work_sync(&priv->unthrottle_work); } From 320f9028c7873c3c7710e8e93e5c979f4c857490 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Sun, 25 Oct 2020 18:45:52 +0100 Subject: [PATCH 027/172] USB: serial: keyspan_pda: fix write unthrottling The driver did not update its view of the available device buffer space until write() was called in task context. This meant that write_room() would return 0 even after the device had sent a write-unthrottle notification, something which could lead to blocked writers not being woken up (e.g. when using OPOST). Note that we must also request an unthrottle notification is case a write() request fills the device buffer exactly. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable Acked-by: Sebastian Andrzej Siewior Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/keyspan_pda.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index 781b6723379f..39ed3ad32365 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -40,6 +40,8 @@ #define DRIVER_AUTHOR "Brian Warner " #define DRIVER_DESC "USB Keyspan PDA Converter driver" +#define KEYSPAN_TX_THRESHOLD 16 + struct keyspan_pda_private { int tx_room; int tx_throttled; @@ -110,7 +112,7 @@ static void keyspan_pda_request_unthrottle(struct work_struct *work) 7, /* request_unthrottle */ USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, - 16, /* value: threshold */ + KEYSPAN_TX_THRESHOLD, 0, /* index */ NULL, 0, @@ -129,6 +131,8 @@ static void keyspan_pda_rx_interrupt(struct urb *urb) int retval; int status = urb->status; struct keyspan_pda_private *priv; + unsigned long flags; + priv = usb_get_serial_port_data(port); switch (status) { @@ -171,7 +175,10 @@ static void keyspan_pda_rx_interrupt(struct urb *urb) case 1: /* modemline change */ break; case 2: /* tx unthrottle interrupt */ + spin_lock_irqsave(&port->lock, flags); priv->tx_throttled = 0; + priv->tx_room = max(priv->tx_room, KEYSPAN_TX_THRESHOLD); + spin_unlock_irqrestore(&port->lock, flags); /* queue up a wakeup at scheduler time */ usb_serial_port_softint(port); break; @@ -505,7 +512,8 @@ static int keyspan_pda_write(struct tty_struct *tty, goto exit; } } - if (count > priv->tx_room) { + + if (count >= priv->tx_room) { /* we're about to completely fill the Tx buffer, so we'll be throttled afterwards. */ count = priv->tx_room; @@ -560,14 +568,17 @@ static void keyspan_pda_write_bulk_callback(struct urb *urb) static int keyspan_pda_write_room(struct tty_struct *tty) { struct usb_serial_port *port = tty->driver_data; - struct keyspan_pda_private *priv; - priv = usb_get_serial_port_data(port); - /* used by n_tty.c for processing of tabs and such. Giving it our - conservative guess is probably good enough, but needs testing by - running a console through the device. */ - return priv->tx_room; -} + struct keyspan_pda_private *priv = usb_get_serial_port_data(port); + unsigned long flags; + int room = 0; + spin_lock_irqsave(&port->lock, flags); + if (test_bit(0, &port->write_urbs_free) && !priv->tx_throttled) + room = priv->tx_room; + spin_unlock_irqrestore(&port->lock, flags); + + return room; +} static int keyspan_pda_chars_in_buffer(struct tty_struct *tty) { From 79fe6826a5ebee2724d432a736ec04d8dca143ba Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Sun, 25 Oct 2020 18:45:53 +0100 Subject: [PATCH 028/172] USB: serial: keyspan_pda: refactor write-room handling Add helper to retrieve the available device transfer-buffer space. Acked-by: Sebastian Andrzej Siewior Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/keyspan_pda.c | 115 ++++++++++++++----------------- 1 file changed, 51 insertions(+), 64 deletions(-) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index 39ed3ad32365..54a21a99c001 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -98,6 +98,42 @@ static const struct usb_device_id id_table_fake_xircom[] = { }; #endif +static int keyspan_pda_get_write_room(struct keyspan_pda_private *priv) +{ + struct usb_serial_port *port = priv->port; + struct usb_serial *serial = priv->serial; + u8 *room; + int rc; + + room = kmalloc(1, GFP_KERNEL); + if (!room) + return -ENOMEM; + + rc = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + 6, /* write_room */ + USB_TYPE_VENDOR | USB_RECIP_INTERFACE + | USB_DIR_IN, + 0, /* value: 0 means "remaining room" */ + 0, /* index */ + room, + 1, + 2000); + if (rc != 1) { + if (rc >= 0) + rc = -EIO; + dev_dbg(&port->dev, "roomquery failed: %d\n", rc); + goto out_free; + } + + dev_dbg(&port->dev, "roomquery says %d\n", *room); + rc = *room; +out_free: + kfree(room); + + return rc; +} + static void keyspan_pda_request_unthrottle(struct work_struct *work) { struct keyspan_pda_private *priv = @@ -436,7 +472,6 @@ static int keyspan_pda_tiocmset(struct tty_struct *tty, static int keyspan_pda_write(struct tty_struct *tty, struct usb_serial_port *port, const unsigned char *buf, int count) { - struct usb_serial *serial = port->serial; int request_unthrottle = 0; int rc = 0; struct keyspan_pda_private *priv; @@ -479,38 +514,11 @@ static int keyspan_pda_write(struct tty_struct *tty, device how much room it really has. This is done only on scheduler time, since usb_control_msg() sleeps. */ if (count > priv->tx_room && !in_interrupt()) { - u8 *room; + rc = keyspan_pda_get_write_room(priv); + if (rc < 0) + goto exit; - room = kmalloc(1, GFP_KERNEL); - if (!room) { - rc = -ENOMEM; - goto exit; - } - - rc = usb_control_msg(serial->dev, - usb_rcvctrlpipe(serial->dev, 0), - 6, /* write_room */ - USB_TYPE_VENDOR | USB_RECIP_INTERFACE - | USB_DIR_IN, - 0, /* value: 0 means "remaining room" */ - 0, /* index */ - room, - 1, - 2000); - if (rc > 0) { - dev_dbg(&port->dev, "roomquery says %d\n", *room); - priv->tx_room = *room; - } - kfree(room); - if (rc < 0) { - dev_dbg(&port->dev, "roomquery failed\n"); - goto exit; - } - if (rc == 0) { - dev_dbg(&port->dev, "roomquery returned 0 bytes\n"); - rc = -EIO; /* device didn't return any data */ - goto exit; - } + priv->tx_room = rc; } if (count >= priv->tx_room) { @@ -614,48 +622,27 @@ static void keyspan_pda_dtr_rts(struct usb_serial_port *port, int on) static int keyspan_pda_open(struct tty_struct *tty, struct usb_serial_port *port) { - struct usb_serial *serial = port->serial; - u8 *room; - int rc = 0; - struct keyspan_pda_private *priv; + struct keyspan_pda_private *priv = usb_get_serial_port_data(port); + int rc; /* find out how much room is in the Tx ring */ - room = kmalloc(1, GFP_KERNEL); - if (!room) - return -ENOMEM; + rc = keyspan_pda_get_write_room(priv); + if (rc < 0) + return rc; - rc = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), - 6, /* write_room */ - USB_TYPE_VENDOR | USB_RECIP_INTERFACE - | USB_DIR_IN, - 0, /* value */ - 0, /* index */ - room, - 1, - 2000); - if (rc < 0) { - dev_dbg(&port->dev, "%s - roomquery failed\n", __func__); - goto error; - } - if (rc == 0) { - dev_dbg(&port->dev, "%s - roomquery returned 0 bytes\n", __func__); - rc = -EIO; - goto error; - } - priv = usb_get_serial_port_data(port); - priv->tx_room = *room; - priv->tx_throttled = *room ? 0 : 1; + priv->tx_room = rc; + priv->tx_throttled = rc ? 0 : 1; /*Start reading from the device*/ rc = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); if (rc) { dev_dbg(&port->dev, "%s - usb_submit_urb(read int) failed\n", __func__); - goto error; + return rc; } -error: - kfree(room); - return rc; + + return 0; } + static void keyspan_pda_close(struct usb_serial_port *port) { struct keyspan_pda_private *priv = usb_get_serial_port_data(port); From 7184933b52a6b3e64171819b77f0bab018696cb2 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Sun, 25 Oct 2020 18:45:54 +0100 Subject: [PATCH 029/172] USB: serial: keyspan_pda: fix write implementation Fix stalled writes by checking the available buffer space after requesting an unthrottle notification in case the device buffer is already empty so that no notification is ever sent (e.g. when doing single character writes). This also means we can drop the room query from write() which was conditioned on in_interrupt() and prevented writing using this driver from atomic contexts (e.g. PPP). Acked-by: Sebastian Andrzej Siewior Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/keyspan_pda.c | 117 +++++++++++++++---------------- 1 file changed, 55 insertions(+), 62 deletions(-) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index 54a21a99c001..b3fb2ecefb31 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -44,7 +44,6 @@ struct keyspan_pda_private { int tx_room; - int tx_throttled; struct work_struct unthrottle_work; struct usb_serial *serial; struct usb_serial_port *port; @@ -138,9 +137,13 @@ static void keyspan_pda_request_unthrottle(struct work_struct *work) { struct keyspan_pda_private *priv = container_of(work, struct keyspan_pda_private, unthrottle_work); + struct usb_serial_port *port = priv->port; struct usb_serial *serial = priv->serial; + unsigned long flags; int result; + dev_dbg(&port->dev, "%s\n", __func__); + /* ask the device to tell us when the tx buffer becomes sufficiently empty */ result = usb_control_msg(serial->dev, @@ -156,8 +159,19 @@ static void keyspan_pda_request_unthrottle(struct work_struct *work) if (result < 0) dev_dbg(&serial->dev->dev, "%s - error %d from usb_control_msg\n", __func__, result); -} + /* + * Need to check available space after requesting notification in case + * buffer is already empty so that no notification is sent. + */ + result = keyspan_pda_get_write_room(priv); + if (result > KEYSPAN_TX_THRESHOLD) { + spin_lock_irqsave(&port->lock, flags); + priv->tx_room = max(priv->tx_room, result); + spin_unlock_irqrestore(&port->lock, flags); + usb_serial_port_softint(port); + } +} static void keyspan_pda_rx_interrupt(struct urb *urb) { @@ -212,7 +226,6 @@ static void keyspan_pda_rx_interrupt(struct urb *urb) break; case 2: /* tx unthrottle interrupt */ spin_lock_irqsave(&port->lock, flags); - priv->tx_throttled = 0; priv->tx_room = max(priv->tx_room, KEYSPAN_TX_THRESHOLD); spin_unlock_irqrestore(&port->lock, flags); /* queue up a wakeup at scheduler time */ @@ -472,35 +485,42 @@ static int keyspan_pda_tiocmset(struct tty_struct *tty, static int keyspan_pda_write(struct tty_struct *tty, struct usb_serial_port *port, const unsigned char *buf, int count) { - int request_unthrottle = 0; - int rc = 0; struct keyspan_pda_private *priv; unsigned long flags; + int room; + int rc; priv = usb_get_serial_port_data(port); - /* guess how much room is left in the device's ring buffer, and if we - want to send more than that, check first, updating our notion of - what is left. If our write will result in no room left, ask the - device to give us an interrupt when the room available rises above - a threshold, and hold off all writers (eventually, those using - select() or poll() too) until we receive that unthrottle interrupt. - Block if we can't write anything at all, otherwise write as much as - we can. */ + /* + * Guess how much room is left in the device's ring buffer. If our + * write will result in no room left, ask the device to give us an + * interrupt when the room available rises above a threshold but also + * query how much room is currently available (in case our guess was + * too conservative and the buffer is already empty when the + * unthrottle work is scheduled). + */ if (count == 0) { dev_dbg(&port->dev, "write request of 0 bytes\n"); return 0; } + if (count > port->bulk_out_size) + count = port->bulk_out_size; + /* we might block because of: the TX urb is in-flight (wait until it completes) the device is full (wait until it says there is room) */ spin_lock_irqsave(&port->lock, flags); - if (!test_bit(0, &port->write_urbs_free) || priv->tx_throttled) { + room = priv->tx_room; + if (!test_bit(0, &port->write_urbs_free) || room == 0) { spin_unlock_irqrestore(&port->lock, flags); return 0; } clear_bit(0, &port->write_urbs_free); + if (count > room) + count = room; + priv->tx_room -= count; spin_unlock_irqrestore(&port->lock, flags); /* At this point the URB is in our control, nobody else can submit it @@ -508,57 +528,29 @@ static int keyspan_pda_write(struct tty_struct *tty, finished). Also, the tx process is not throttled. So we are ready to write. */ - count = (count > port->bulk_out_size) ? port->bulk_out_size : count; + dev_dbg(&port->dev, "%s - count = %d, txroom = %d\n", __func__, count, room); - /* Check if we might overrun the Tx buffer. If so, ask the - device how much room it really has. This is done only on - scheduler time, since usb_control_msg() sleeps. */ - if (count > priv->tx_room && !in_interrupt()) { - rc = keyspan_pda_get_write_room(priv); - if (rc < 0) - goto exit; + memcpy(port->write_urb->transfer_buffer, buf, count); + port->write_urb->transfer_buffer_length = count; - priv->tx_room = rc; - } + rc = usb_submit_urb(port->write_urb, GFP_ATOMIC); + if (rc) { + dev_dbg(&port->dev, "usb_submit_urb(write bulk) failed\n"); - if (count >= priv->tx_room) { - /* we're about to completely fill the Tx buffer, so - we'll be throttled afterwards. */ - count = priv->tx_room; - request_unthrottle = 1; - } + spin_lock_irqsave(&port->lock, flags); + priv->tx_room = max(priv->tx_room, room + count); + spin_unlock_irqrestore(&port->lock, flags); - if (count) { - /* now transfer data */ - memcpy(port->write_urb->transfer_buffer, buf, count); - /* send the data out the bulk port */ - port->write_urb->transfer_buffer_length = count; - - priv->tx_room -= count; - - rc = usb_submit_urb(port->write_urb, GFP_ATOMIC); - if (rc) { - dev_dbg(&port->dev, "usb_submit_urb(write bulk) failed\n"); - goto exit; - } - } else { - /* There wasn't any room left, so we are throttled until - the buffer empties a bit */ - request_unthrottle = 1; - } - - if (request_unthrottle) { - priv->tx_throttled = 1; /* block writers */ - schedule_work(&priv->unthrottle_work); - } - - rc = count; -exit: - if (rc <= 0) set_bit(0, &port->write_urbs_free); - return rc; -} + return rc; + } + + if (count == room) + schedule_work(&priv->unthrottle_work); + + return count; +} static void keyspan_pda_write_bulk_callback(struct urb *urb) { @@ -581,7 +573,7 @@ static int keyspan_pda_write_room(struct tty_struct *tty) int room = 0; spin_lock_irqsave(&port->lock, flags); - if (test_bit(0, &port->write_urbs_free) && !priv->tx_throttled) + if (test_bit(0, &port->write_urbs_free)) room = priv->tx_room; spin_unlock_irqrestore(&port->lock, flags); @@ -601,7 +593,7 @@ static int keyspan_pda_chars_in_buffer(struct tty_struct *tty) n_tty.c:normal_poll() ) that we're not writeable. */ spin_lock_irqsave(&port->lock, flags); - if (!test_bit(0, &port->write_urbs_free) || priv->tx_throttled) + if (!test_bit(0, &port->write_urbs_free) || priv->tx_room == 0) ret = 256; spin_unlock_irqrestore(&port->lock, flags); return ret; @@ -630,8 +622,9 @@ static int keyspan_pda_open(struct tty_struct *tty, if (rc < 0) return rc; + spin_lock_irq(&port->lock); priv->tx_room = rc; - priv->tx_throttled = rc ? 0 : 1; + spin_unlock_irq(&port->lock); /*Start reading from the device*/ rc = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); From 6fded8bcbc2e34d2267442376bf4fe9dde196d65 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Sun, 25 Oct 2020 18:45:55 +0100 Subject: [PATCH 030/172] USB: serial: keyspan_pda: increase transmitter threshold Increase the transmitter threshold so that writing isn't resumed until 128 bytes are available in the device buffer thereby allowing for larger and more efficient transfers. Acked-by: Sebastian Andrzej Siewior Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/keyspan_pda.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index b3fb2ecefb31..3816bbc928b2 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -40,7 +40,7 @@ #define DRIVER_AUTHOR "Brian Warner " #define DRIVER_DESC "USB Keyspan PDA Converter driver" -#define KEYSPAN_TX_THRESHOLD 16 +#define KEYSPAN_TX_THRESHOLD 128 struct keyspan_pda_private { int tx_room; From 034e38e8f68767fb5438ae3e608ee82919674177 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Sun, 25 Oct 2020 18:45:56 +0100 Subject: [PATCH 031/172] USB: serial: keyspan_pda: add write-fifo support Use the port write fifo and generic chars_and_buffer and write_room implementations when writing. This not only allows for more efficient transfers, but more importantly fixes the remaining issues related to the conservative write_room() implementation which could prevent the line discipline from making forward progress (e.g. waiting for n > 1 bytes of space to become available). Note that this also allows using the driver for the system console without dropping data when the write URB is busy (including when adding carriage return on line feed). Acked-by: Sebastian Andrzej Siewior Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/keyspan_pda.c | 123 +++++++++++++++---------------- 1 file changed, 59 insertions(+), 64 deletions(-) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index 3816bbc928b2..aa19dd181e42 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -5,6 +5,7 @@ * Copyright (C) 1999 - 2001 Greg Kroah-Hartman * Copyright (C) 1999, 2000 Brian Warner * Copyright (C) 2000 Al Borchers + * Copyright (C) 2020 Johan Hovold * * See Documentation/usb/usb-serial.rst for more information on using this * driver @@ -37,7 +38,7 @@ #undef XIRCOM #endif -#define DRIVER_AUTHOR "Brian Warner " +#define DRIVER_AUTHOR "Brian Warner , Johan Hovold " #define DRIVER_DESC "USB Keyspan PDA Converter driver" #define KEYSPAN_TX_THRESHOLD 128 @@ -49,6 +50,7 @@ struct keyspan_pda_private { struct usb_serial_port *port; }; +static int keyspan_pda_write_start(struct usb_serial_port *port); #define KEYSPAN_VENDOR_ID 0x06cd #define KEYSPAN_PDA_FAKE_ID 0x0103 @@ -228,6 +230,9 @@ static void keyspan_pda_rx_interrupt(struct urb *urb) spin_lock_irqsave(&port->lock, flags); priv->tx_room = max(priv->tx_room, KEYSPAN_TX_THRESHOLD); spin_unlock_irqrestore(&port->lock, flags); + + keyspan_pda_write_start(port); + /* queue up a wakeup at scheduler time */ usb_serial_port_softint(port); break; @@ -482,15 +487,15 @@ static int keyspan_pda_tiocmset(struct tty_struct *tty, return rc; } -static int keyspan_pda_write(struct tty_struct *tty, - struct usb_serial_port *port, const unsigned char *buf, int count) +static int keyspan_pda_write_start(struct usb_serial_port *port) { - struct keyspan_pda_private *priv; + struct keyspan_pda_private *priv = usb_get_serial_port_data(port); unsigned long flags; + struct urb *urb; + int count; int room; int rc; - priv = usb_get_serial_port_data(port); /* * Guess how much room is left in the device's ring buffer. If our * write will result in no room left, ask the device to give us an @@ -499,50 +504,48 @@ static int keyspan_pda_write(struct tty_struct *tty, * too conservative and the buffer is already empty when the * unthrottle work is scheduled). */ - if (count == 0) { - dev_dbg(&port->dev, "write request of 0 bytes\n"); - return 0; - } - - if (count > port->bulk_out_size) - count = port->bulk_out_size; /* we might block because of: the TX urb is in-flight (wait until it completes) the device is full (wait until it says there is room) */ spin_lock_irqsave(&port->lock, flags); + room = priv->tx_room; - if (!test_bit(0, &port->write_urbs_free) || room == 0) { + count = kfifo_len(&port->write_fifo); + + if (!test_bit(0, &port->write_urbs_free) || count == 0 || room == 0) { spin_unlock_irqrestore(&port->lock, flags); return 0; } - clear_bit(0, &port->write_urbs_free); + __clear_bit(0, &port->write_urbs_free); + if (count > room) count = room; - priv->tx_room -= count; - spin_unlock_irqrestore(&port->lock, flags); + if (count > port->bulk_out_size) + count = port->bulk_out_size; - /* At this point the URB is in our control, nobody else can submit it - again (the only sudden transition was the one from EINPROGRESS to - finished). Also, the tx process is not throttled. So we are - ready to write. */ + urb = port->write_urb; + count = kfifo_out(&port->write_fifo, urb->transfer_buffer, count); + urb->transfer_buffer_length = count; + + port->tx_bytes += count; + priv->tx_room -= count; + + spin_unlock_irqrestore(&port->lock, flags); dev_dbg(&port->dev, "%s - count = %d, txroom = %d\n", __func__, count, room); - memcpy(port->write_urb->transfer_buffer, buf, count); - port->write_urb->transfer_buffer_length = count; - - rc = usb_submit_urb(port->write_urb, GFP_ATOMIC); + rc = usb_submit_urb(urb, GFP_ATOMIC); if (rc) { dev_dbg(&port->dev, "usb_submit_urb(write bulk) failed\n"); spin_lock_irqsave(&port->lock, flags); + port->tx_bytes -= count; priv->tx_room = max(priv->tx_room, room + count); + __set_bit(0, &port->write_urbs_free); spin_unlock_irqrestore(&port->lock, flags); - set_bit(0, &port->write_urbs_free); - return rc; } @@ -555,51 +558,38 @@ static int keyspan_pda_write(struct tty_struct *tty, static void keyspan_pda_write_bulk_callback(struct urb *urb) { struct usb_serial_port *port = urb->context; - struct keyspan_pda_private *priv; + unsigned long flags; - set_bit(0, &port->write_urbs_free); - priv = usb_get_serial_port_data(port); + spin_lock_irqsave(&port->lock, flags); + port->tx_bytes -= urb->transfer_buffer_length; + __set_bit(0, &port->write_urbs_free); + spin_unlock_irqrestore(&port->lock, flags); + + keyspan_pda_write_start(port); /* queue up a wakeup at scheduler time */ usb_serial_port_softint(port); } - -static int keyspan_pda_write_room(struct tty_struct *tty) +static int keyspan_pda_write(struct tty_struct *tty, struct usb_serial_port *port, + const unsigned char *buf, int count) { - struct usb_serial_port *port = tty->driver_data; - struct keyspan_pda_private *priv = usb_get_serial_port_data(port); - unsigned long flags; - int room = 0; + int rc; - spin_lock_irqsave(&port->lock, flags); - if (test_bit(0, &port->write_urbs_free)) - room = priv->tx_room; - spin_unlock_irqrestore(&port->lock, flags); + dev_dbg(&port->dev, "%s - count = %d\n", __func__, count); - return room; + if (!count) + return 0; + + count = kfifo_in_locked(&port->write_fifo, buf, count, &port->lock); + + rc = keyspan_pda_write_start(port); + if (rc) + return rc; + + return count; } -static int keyspan_pda_chars_in_buffer(struct tty_struct *tty) -{ - struct usb_serial_port *port = tty->driver_data; - struct keyspan_pda_private *priv; - unsigned long flags; - int ret = 0; - - priv = usb_get_serial_port_data(port); - - /* when throttled, return at least WAKEUP_CHARS to tell select() (via - n_tty.c:normal_poll() ) that we're not writeable. */ - - spin_lock_irqsave(&port->lock, flags); - if (!test_bit(0, &port->write_urbs_free) || priv->tx_room == 0) - ret = 256; - spin_unlock_irqrestore(&port->lock, flags); - return ret; -} - - static void keyspan_pda_dtr_rts(struct usb_serial_port *port, int on) { struct usb_serial *serial = port->serial; @@ -640,12 +630,19 @@ static void keyspan_pda_close(struct usb_serial_port *port) { struct keyspan_pda_private *priv = usb_get_serial_port_data(port); - usb_kill_urb(port->write_urb); + /* + * Stop the interrupt URB first as its completion handler may submit + * the write URB. + */ usb_kill_urb(port->interrupt_in_urb); + usb_kill_urb(port->write_urb); cancel_work_sync(&priv->unthrottle_work); -} + spin_lock_irq(&port->lock); + kfifo_reset(&port->write_fifo); + spin_unlock_irq(&port->lock); +} /* download the firmware to a "fake" device (pre-renumeration) */ static int keyspan_pda_fake_startup(struct usb_serial *serial) @@ -759,10 +756,8 @@ static struct usb_serial_driver keyspan_pda_device = { .open = keyspan_pda_open, .close = keyspan_pda_close, .write = keyspan_pda_write, - .write_room = keyspan_pda_write_room, .write_bulk_callback = keyspan_pda_write_bulk_callback, .read_int_callback = keyspan_pda_rx_interrupt, - .chars_in_buffer = keyspan_pda_chars_in_buffer, .throttle = keyspan_pda_rx_throttle, .unthrottle = keyspan_pda_rx_unthrottle, .set_termios = keyspan_pda_set_termios, From 7604ce70b8f675582ae68064e125be4f5174dec0 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 27 Oct 2020 10:25:02 +0100 Subject: [PATCH 032/172] USB: serial: keyspan_pda: clean up xircom/entrega support Drop the separate Kconfig symbol for Xircom / Entrega and always include support in the keyspan_pda driver. Note that all configs that enabled CONFIG_USB_SERIAL_XIRCOM also enable CONFIG_USB_SERIAL_KEYSPAN_PDA. Acked-by: Sebastian Andrzej Siewior Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- arch/arm/configs/badge4_defconfig | 1 - arch/arm/configs/corgi_defconfig | 1 - arch/arm/configs/pxa_defconfig | 1 - arch/arm/configs/spitz_defconfig | 1 - arch/mips/configs/mtx1_defconfig | 1 - arch/mips/configs/rm200_defconfig | 1 - arch/powerpc/configs/g5_defconfig | 1 - arch/powerpc/configs/ppc6xx_defconfig | 1 - drivers/usb/serial/Kconfig | 19 ++------ drivers/usb/serial/Makefile | 1 - drivers/usb/serial/keyspan_pda.c | 68 ++++----------------------- 11 files changed, 13 insertions(+), 83 deletions(-) diff --git a/arch/arm/configs/badge4_defconfig b/arch/arm/configs/badge4_defconfig index ef484c4cfd1a..d9119da65f48 100644 --- a/arch/arm/configs/badge4_defconfig +++ b/arch/arm/configs/badge4_defconfig @@ -89,7 +89,6 @@ CONFIG_USB_SERIAL_KEYSPAN=m CONFIG_USB_SERIAL_MCT_U232=m CONFIG_USB_SERIAL_PL2303=m CONFIG_USB_SERIAL_CYBERJACK=m -CONFIG_USB_SERIAL_XIRCOM=m CONFIG_USB_SERIAL_OMNINET=m CONFIG_EXT2_FS=m CONFIG_EXT3_FS=m diff --git a/arch/arm/configs/corgi_defconfig b/arch/arm/configs/corgi_defconfig index 4fec2ec379ad..911e880f06ed 100644 --- a/arch/arm/configs/corgi_defconfig +++ b/arch/arm/configs/corgi_defconfig @@ -191,7 +191,6 @@ CONFIG_USB_SERIAL_PL2303=m CONFIG_USB_SERIAL_SAFE=m CONFIG_USB_SERIAL_TI=m CONFIG_USB_SERIAL_CYBERJACK=m -CONFIG_USB_SERIAL_XIRCOM=m CONFIG_USB_SERIAL_OMNINET=m CONFIG_USB_EMI62=m CONFIG_USB_EMI26=m diff --git a/arch/arm/configs/pxa_defconfig b/arch/arm/configs/pxa_defconfig index d7b9eaf4783c..8654ece13004 100644 --- a/arch/arm/configs/pxa_defconfig +++ b/arch/arm/configs/pxa_defconfig @@ -574,7 +574,6 @@ CONFIG_USB_SERIAL_PL2303=m CONFIG_USB_SERIAL_SAFE=m CONFIG_USB_SERIAL_TI=m CONFIG_USB_SERIAL_CYBERJACK=m -CONFIG_USB_SERIAL_XIRCOM=m CONFIG_USB_SERIAL_OMNINET=m CONFIG_USB_EMI62=m CONFIG_USB_EMI26=m diff --git a/arch/arm/configs/spitz_defconfig b/arch/arm/configs/spitz_defconfig index a1cdbfa064c5..8b2c14424927 100644 --- a/arch/arm/configs/spitz_defconfig +++ b/arch/arm/configs/spitz_defconfig @@ -185,7 +185,6 @@ CONFIG_USB_SERIAL_PL2303=m CONFIG_USB_SERIAL_SAFE=m CONFIG_USB_SERIAL_TI=m CONFIG_USB_SERIAL_CYBERJACK=m -CONFIG_USB_SERIAL_XIRCOM=m CONFIG_USB_SERIAL_OMNINET=m CONFIG_USB_EMI62=m CONFIG_USB_EMI26=m diff --git a/arch/mips/configs/mtx1_defconfig b/arch/mips/configs/mtx1_defconfig index 914af125a7fa..9750bcc38f05 100644 --- a/arch/mips/configs/mtx1_defconfig +++ b/arch/mips/configs/mtx1_defconfig @@ -565,7 +565,6 @@ CONFIG_USB_SERIAL_SAFE=m CONFIG_USB_SERIAL_SIERRAWIRELESS=m CONFIG_USB_SERIAL_TI=m CONFIG_USB_SERIAL_CYBERJACK=m -CONFIG_USB_SERIAL_XIRCOM=m CONFIG_USB_SERIAL_OPTION=m CONFIG_USB_SERIAL_OMNINET=m CONFIG_USB_EMI62=m diff --git a/arch/mips/configs/rm200_defconfig b/arch/mips/configs/rm200_defconfig index 30d7c3db884e..3dc2da2bee0d 100644 --- a/arch/mips/configs/rm200_defconfig +++ b/arch/mips/configs/rm200_defconfig @@ -311,7 +311,6 @@ CONFIG_USB_SERIAL_PL2303=m CONFIG_USB_SERIAL_SAFE=m CONFIG_USB_SERIAL_SAFE_PADDED=y CONFIG_USB_SERIAL_CYBERJACK=m -CONFIG_USB_SERIAL_XIRCOM=m CONFIG_USB_SERIAL_OMNINET=m CONFIG_USB_LEGOTOWER=m CONFIG_USB_LCD=m diff --git a/arch/powerpc/configs/g5_defconfig b/arch/powerpc/configs/g5_defconfig index 1c674c4c1d86..1de0dbf6cbba 100644 --- a/arch/powerpc/configs/g5_defconfig +++ b/arch/powerpc/configs/g5_defconfig @@ -194,7 +194,6 @@ CONFIG_USB_SERIAL_SAFE=m CONFIG_USB_SERIAL_SAFE_PADDED=y CONFIG_USB_SERIAL_TI=m CONFIG_USB_SERIAL_CYBERJACK=m -CONFIG_USB_SERIAL_XIRCOM=m CONFIG_USB_SERIAL_OMNINET=m CONFIG_USB_APPLEDISPLAY=m CONFIG_EXT2_FS=y diff --git a/arch/powerpc/configs/ppc6xx_defconfig b/arch/powerpc/configs/ppc6xx_defconfig index 66e9a0fd64ff..ac92cbe1f581 100644 --- a/arch/powerpc/configs/ppc6xx_defconfig +++ b/arch/powerpc/configs/ppc6xx_defconfig @@ -911,7 +911,6 @@ CONFIG_USB_SERIAL_SAFE_PADDED=y CONFIG_USB_SERIAL_SIERRAWIRELESS=m CONFIG_USB_SERIAL_TI=m CONFIG_USB_SERIAL_CYBERJACK=m -CONFIG_USB_SERIAL_XIRCOM=m CONFIG_USB_SERIAL_OPTION=m CONFIG_USB_SERIAL_OMNINET=m CONFIG_USB_SERIAL_DEBUG=m diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index 4007fa25a8ff..a21ff5ab6df9 100644 --- a/drivers/usb/serial/Kconfig +++ b/drivers/usb/serial/Kconfig @@ -298,12 +298,12 @@ config USB_SERIAL_IUU module will be called iuu_phoenix.o config USB_SERIAL_KEYSPAN_PDA - tristate "USB Keyspan PDA Single Port Serial Driver" + tristate "USB Keyspan PDA / Xircom Single Port Serial Driver" select USB_EZUSB_FX2 help - Say Y here if you want to use a Keyspan PDA single port USB to - serial converter device. This driver makes use of firmware - developed from scratch by Brian Warner. + Say Y here if you want to use a Keyspan PDA, Xircom or Entrega single + port USB to serial converter device. This driver makes use of + firmware developed from scratch by Brian Warner. To compile this driver as a module, choose M here: the module will be called keyspan_pda. @@ -538,17 +538,6 @@ config USB_SERIAL_CYBERJACK If unsure, say N. -config USB_SERIAL_XIRCOM - tristate "USB Xircom / Entrega Single Port Serial Driver" - select USB_EZUSB_FX2 - help - Say Y here if you want to use a Xircom or Entrega single port USB to - serial converter device. This driver makes use of firmware - developed from scratch by Brian Warner. - - To compile this driver as a module, choose M here: the - module will be called keyspan_pda. - config USB_SERIAL_WWAN tristate diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile index 2d491e434f11..50c53aed787a 100644 --- a/drivers/usb/serial/Makefile +++ b/drivers/usb/serial/Makefile @@ -61,5 +61,4 @@ obj-$(CONFIG_USB_SERIAL_UPD78F0730) += upd78f0730.o obj-$(CONFIG_USB_SERIAL_VISOR) += visor.o obj-$(CONFIG_USB_SERIAL_WISHBONE) += wishbone-serial.o obj-$(CONFIG_USB_SERIAL_WHITEHEAT) += whiteheat.o -obj-$(CONFIG_USB_SERIAL_XIRCOM) += keyspan_pda.o obj-$(CONFIG_USB_SERIAL_XSENS_MT) += xsens_mt.o diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index aa19dd181e42..f102dbf83492 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -26,18 +26,6 @@ #include #include -/* make a simple define to handle if we are compiling keyspan_pda or xircom support */ -#if IS_ENABLED(CONFIG_USB_SERIAL_KEYSPAN_PDA) - #define KEYSPAN -#else - #undef KEYSPAN -#endif -#if IS_ENABLED(CONFIG_USB_SERIAL_XIRCOM) - #define XIRCOM -#else - #undef XIRCOM -#endif - #define DRIVER_AUTHOR "Brian Warner , Johan Hovold " #define DRIVER_DESC "USB Keyspan PDA Converter driver" @@ -64,18 +52,13 @@ static int keyspan_pda_write_start(struct usb_serial_port *port); #define ENTREGA_FAKE_ID 0x8093 static const struct usb_device_id id_table_combined[] = { -#ifdef KEYSPAN { USB_DEVICE(KEYSPAN_VENDOR_ID, KEYSPAN_PDA_FAKE_ID) }, -#endif -#ifdef XIRCOM { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID) }, { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID_2) }, { USB_DEVICE(ENTREGA_VENDOR_ID, ENTREGA_FAKE_ID) }, -#endif { USB_DEVICE(KEYSPAN_VENDOR_ID, KEYSPAN_PDA_ID) }, { } /* Terminating entry */ }; - MODULE_DEVICE_TABLE(usb, id_table_combined); static const struct usb_device_id id_table_std[] = { @@ -83,21 +66,13 @@ static const struct usb_device_id id_table_std[] = { { } /* Terminating entry */ }; -#ifdef KEYSPAN static const struct usb_device_id id_table_fake[] = { { USB_DEVICE(KEYSPAN_VENDOR_ID, KEYSPAN_PDA_FAKE_ID) }, - { } /* Terminating entry */ -}; -#endif - -#ifdef XIRCOM -static const struct usb_device_id id_table_fake_xircom[] = { { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID) }, { USB_DEVICE(XIRCOM_VENDOR_ID, XIRCOM_FAKE_ID_2) }, { USB_DEVICE(ENTREGA_VENDOR_ID, ENTREGA_FAKE_ID) }, - { } + { } /* Terminating entry */ }; -#endif static int keyspan_pda_get_write_room(struct keyspan_pda_private *priv) { @@ -647,22 +622,21 @@ static void keyspan_pda_close(struct usb_serial_port *port) /* download the firmware to a "fake" device (pre-renumeration) */ static int keyspan_pda_fake_startup(struct usb_serial *serial) { + unsigned int vid = le16_to_cpu(serial->dev->descriptor.idVendor); const char *fw_name; /* download the firmware here ... */ ezusb_fx1_set_reset(serial->dev, 1); - if (0) { ; } -#ifdef KEYSPAN - else if (le16_to_cpu(serial->dev->descriptor.idVendor) == KEYSPAN_VENDOR_ID) + switch (vid) { + case KEYSPAN_VENDOR_ID: fw_name = "keyspan_pda/keyspan_pda.fw"; -#endif -#ifdef XIRCOM - else if ((le16_to_cpu(serial->dev->descriptor.idVendor) == XIRCOM_VENDOR_ID) || - (le16_to_cpu(serial->dev->descriptor.idVendor) == ENTREGA_VENDOR_ID)) + break; + case XIRCOM_VENDOR_ID: + case ENTREGA_VENDOR_ID: fw_name = "keyspan_pda/xircom_pgs.fw"; -#endif - else { + break; + default: dev_err(&serial->dev->dev, "%s: unknown vendor, aborting.\n", __func__); return -ENODEV; @@ -681,12 +655,8 @@ static int keyspan_pda_fake_startup(struct usb_serial *serial) return 1; } -#ifdef KEYSPAN MODULE_FIRMWARE("keyspan_pda/keyspan_pda.fw"); -#endif -#ifdef XIRCOM MODULE_FIRMWARE("keyspan_pda/xircom_pgs.fw"); -#endif static int keyspan_pda_port_probe(struct usb_serial_port *port) { @@ -716,7 +686,6 @@ static int keyspan_pda_port_remove(struct usb_serial_port *port) return 0; } -#ifdef KEYSPAN static struct usb_serial_driver keyspan_pda_fake_device = { .driver = { .owner = THIS_MODULE, @@ -727,20 +696,6 @@ static struct usb_serial_driver keyspan_pda_fake_device = { .num_ports = 1, .attach = keyspan_pda_fake_startup, }; -#endif - -#ifdef XIRCOM -static struct usb_serial_driver xircom_pgs_fake_device = { - .driver = { - .owner = THIS_MODULE, - .name = "xircom_no_firm", - }, - .description = "Xircom / Entrega PGS - (prerenumeration)", - .id_table = id_table_fake_xircom, - .num_ports = 1, - .attach = keyspan_pda_fake_startup, -}; -#endif static struct usb_serial_driver keyspan_pda_device = { .driver = { @@ -770,12 +725,7 @@ static struct usb_serial_driver keyspan_pda_device = { static struct usb_serial_driver * const serial_drivers[] = { &keyspan_pda_device, -#ifdef KEYSPAN &keyspan_pda_fake_device, -#endif -#ifdef XIRCOM - &xircom_pgs_fake_device, -#endif NULL }; From 491d6927f0de587c1d322d8b29a1187b7e06a221 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Sun, 25 Oct 2020 18:45:58 +0100 Subject: [PATCH 033/172] USB: serial: keyspan_pda: clean up comments and whitespace Clean up comment style, remove some stale or redundant comments and drop superfluous white space. Acked-by: Sebastian Andrzej Siewior Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/keyspan_pda.c | 116 +++++++++++++++---------------- 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index f102dbf83492..8df7c54d6986 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -11,7 +11,6 @@ * driver */ - #include #include #include @@ -121,8 +120,10 @@ static void keyspan_pda_request_unthrottle(struct work_struct *work) dev_dbg(&port->dev, "%s\n", __func__); - /* ask the device to tell us when the tx buffer becomes - sufficiently empty */ + /* + * Ask the device to tell us when the tx buffer becomes + * sufficiently empty. + */ result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), 7, /* request_unthrottle */ @@ -208,7 +209,6 @@ static void keyspan_pda_rx_interrupt(struct urb *urb) keyspan_pda_write_start(port); - /* queue up a wakeup at scheduler time */ usb_serial_port_softint(port); break; default: @@ -227,31 +227,30 @@ exit: __func__, retval); } - static void keyspan_pda_rx_throttle(struct tty_struct *tty) { - /* stop receiving characters. We just turn off the URB request, and - let chars pile up in the device. If we're doing hardware - flowcontrol, the device will signal the other end when its buffer - fills up. If we're doing XON/XOFF, this would be a good time to - send an XOFF, although it might make sense to foist that off - upon the device too. */ struct usb_serial_port *port = tty->driver_data; + /* + * Stop receiving characters. We just turn off the URB request, and + * let chars pile up in the device. If we're doing hardware + * flowcontrol, the device will signal the other end when its buffer + * fills up. If we're doing XON/XOFF, this would be a good time to + * send an XOFF, although it might make sense to foist that off upon + * the device too. + */ usb_kill_urb(port->interrupt_in_urb); } - static void keyspan_pda_rx_unthrottle(struct tty_struct *tty) { struct usb_serial_port *port = tty->driver_data; - /* just restart the receive interrupt URB */ + /* just restart the receive interrupt URB */ if (usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL)) dev_dbg(&port->dev, "usb_submit_urb(read urb) failed\n"); } - static speed_t keyspan_pda_setbaud(struct usb_serial *serial, speed_t baud) { int rc; @@ -293,8 +292,6 @@ static speed_t keyspan_pda_setbaud(struct usb_serial *serial, speed_t baud) baud = 9600; } - /* rather than figure out how to sleep while waiting for this - to complete, I just use the "legacy" API. */ rc = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), 0, /* set baud */ USB_TYPE_VENDOR @@ -307,10 +304,10 @@ static speed_t keyspan_pda_setbaud(struct usb_serial *serial, speed_t baud) 2000); /* timeout */ if (rc < 0) return 0; + return baud; } - static void keyspan_pda_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; @@ -322,6 +319,7 @@ static void keyspan_pda_break_ctl(struct tty_struct *tty, int break_state) value = 1; /* start break */ else value = 0; /* clear break */ + result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), 4, /* set break */ USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, @@ -329,39 +327,35 @@ static void keyspan_pda_break_ctl(struct tty_struct *tty, int break_state) if (result < 0) dev_dbg(&port->dev, "%s - error %d from usb_control_msg\n", __func__, result); - /* there is something funky about this.. the TCSBRK that 'cu' performs - ought to translate into a break_ctl(-1),break_ctl(0) pair HZ/4 - seconds apart, but it feels like the break sent isn't as long as it - is on /dev/ttyS0 */ } - static void keyspan_pda_set_termios(struct tty_struct *tty, struct usb_serial_port *port, struct ktermios *old_termios) { struct usb_serial *serial = port->serial; speed_t speed; - /* cflag specifies lots of stuff: number of stop bits, parity, number - of data bits, baud. What can the device actually handle?: - CSTOPB (1 stop bit or 2) - PARENB (parity) - CSIZE (5bit .. 8bit) - There is minimal hw support for parity (a PSW bit seems to hold the - parity of whatever is in the accumulator). The UART either deals - with 10 bits (start, 8 data, stop) or 11 bits (start, 8 data, - 1 special, stop). So, with firmware changes, we could do: - 8N1: 10 bit - 8N2: 11 bit, extra bit always (mark?) - 8[EOMS]1: 11 bit, extra bit is parity - 7[EOMS]1: 10 bit, b0/b7 is parity - 7[EOMS]2: 11 bit, b0/b7 is parity, extra bit always (mark?) - - HW flow control is dictated by the tty->termios.c_cflags & CRTSCTS - bit. - - For now, just do baud. */ - + /* + * cflag specifies lots of stuff: number of stop bits, parity, number + * of data bits, baud. What can the device actually handle?: + * CSTOPB (1 stop bit or 2) + * PARENB (parity) + * CSIZE (5bit .. 8bit) + * There is minimal hw support for parity (a PSW bit seems to hold the + * parity of whatever is in the accumulator). The UART either deals + * with 10 bits (start, 8 data, stop) or 11 bits (start, 8 data, + * 1 special, stop). So, with firmware changes, we could do: + * 8N1: 10 bit + * 8N2: 11 bit, extra bit always (mark?) + * 8[EOMS]1: 11 bit, extra bit is parity + * 7[EOMS]1: 10 bit, b0/b7 is parity + * 7[EOMS]2: 11 bit, b0/b7 is parity, extra bit always (mark?) + * + * HW flow control is dictated by the tty->termios.c_cflags & CRTSCTS + * bit. + * + * For now, just do baud. + */ speed = tty_get_baud_rate(tty); speed = keyspan_pda_setbaud(serial, speed); @@ -370,17 +364,19 @@ static void keyspan_pda_set_termios(struct tty_struct *tty, /* It hasn't changed so.. */ speed = tty_termios_baud_rate(old_termios); } - /* Only speed can change so copy the old h/w parameters - then encode the new speed */ + /* + * Only speed can change so copy the old h/w parameters then encode + * the new speed. + */ tty_termios_copy_hw(&tty->termios, old_termios); tty_encode_baud_rate(tty, speed, speed); } - -/* modem control pins: DTR and RTS are outputs and can be controlled. - DCD, RI, DSR, CTS are inputs and can be read. All outputs can also be - read. The byte passed is: DTR(b7) DCD RI DSR CTS RTS(b2) unused unused */ - +/* + * Modem control pins: DTR and RTS are outputs and can be controlled. + * DCD, RI, DSR, CTS are inputs and can be read. All outputs can also be + * read. The byte passed is: DTR(b7) DCD RI DSR CTS RTS(b2) unused unused. + */ static int keyspan_pda_get_modem_info(struct usb_serial *serial, unsigned char *value) { @@ -404,7 +400,6 @@ static int keyspan_pda_get_modem_info(struct usb_serial *serial, return rc; } - static int keyspan_pda_set_modem_info(struct usb_serial *serial, unsigned char value) { @@ -480,10 +475,11 @@ static int keyspan_pda_write_start(struct usb_serial_port *port) * unthrottle work is scheduled). */ - /* we might block because of: - the TX urb is in-flight (wait until it completes) - the device is full (wait until it says there is room) - */ + /* + * We might block because of: + * the TX urb is in-flight (wait until it completes) + * the device is full (wait until it says there is room) + */ spin_lock_irqsave(&port->lock, flags); room = priv->tx_room; @@ -542,7 +538,6 @@ static void keyspan_pda_write_bulk_callback(struct urb *urb) keyspan_pda_write_start(port); - /* queue up a wakeup at scheduler time */ usb_serial_port_softint(port); } @@ -591,7 +586,6 @@ static int keyspan_pda_open(struct tty_struct *tty, priv->tx_room = rc; spin_unlock_irq(&port->lock); - /*Start reading from the device*/ rc = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); if (rc) { dev_dbg(&port->dev, "%s - usb_submit_urb(read int) failed\n", __func__); @@ -648,10 +642,12 @@ static int keyspan_pda_fake_startup(struct usb_serial *serial) return -ENOENT; } - /* after downloading firmware Renumeration will occur in a - moment and the new device will bind to the real driver */ + /* + * After downloading firmware renumeration will occur in a moment and + * the new device will bind to the real driver. + */ - /* we want this device to fail to have a driver assigned to it. */ + /* We want this device to fail to have a driver assigned to it. */ return 1; } @@ -711,7 +707,7 @@ static struct usb_serial_driver keyspan_pda_device = { .open = keyspan_pda_open, .close = keyspan_pda_close, .write = keyspan_pda_write, - .write_bulk_callback = keyspan_pda_write_bulk_callback, + .write_bulk_callback = keyspan_pda_write_bulk_callback, .read_int_callback = keyspan_pda_rx_interrupt, .throttle = keyspan_pda_rx_throttle, .unthrottle = keyspan_pda_rx_unthrottle, From fbbf41f64a8de352b67214c089366f97c7ac1678 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Sun, 25 Oct 2020 18:45:59 +0100 Subject: [PATCH 034/172] USB: serial: keyspan_pda: use BIT() macro Use the BIT() macro instead of open coding. Acked-by: Sebastian Andrzej Siewior Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/keyspan_pda.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index 8df7c54d6986..f582e0a3ae56 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -422,13 +422,14 @@ static int keyspan_pda_tiocmget(struct tty_struct *tty) rc = keyspan_pda_get_modem_info(serial, &status); if (rc < 0) return rc; - value = - ((status & (1<<7)) ? TIOCM_DTR : 0) | - ((status & (1<<6)) ? TIOCM_CAR : 0) | - ((status & (1<<5)) ? TIOCM_RNG : 0) | - ((status & (1<<4)) ? TIOCM_DSR : 0) | - ((status & (1<<3)) ? TIOCM_CTS : 0) | - ((status & (1<<2)) ? TIOCM_RTS : 0); + + value = ((status & BIT(7)) ? TIOCM_DTR : 0) | + ((status & BIT(6)) ? TIOCM_CAR : 0) | + ((status & BIT(5)) ? TIOCM_RNG : 0) | + ((status & BIT(4)) ? TIOCM_DSR : 0) | + ((status & BIT(3)) ? TIOCM_CTS : 0) | + ((status & BIT(2)) ? TIOCM_RTS : 0); + return value; } @@ -445,14 +446,14 @@ static int keyspan_pda_tiocmset(struct tty_struct *tty, return rc; if (set & TIOCM_RTS) - status |= (1<<2); + status |= BIT(2); if (set & TIOCM_DTR) - status |= (1<<7); + status |= BIT(7); if (clear & TIOCM_RTS) - status &= ~(1<<2); + status &= ~BIT(2); if (clear & TIOCM_DTR) - status &= ~(1<<7); + status &= ~BIT(7); rc = keyspan_pda_set_modem_info(serial, status); return rc; } @@ -565,7 +566,7 @@ static void keyspan_pda_dtr_rts(struct usb_serial_port *port, int on) struct usb_serial *serial = port->serial; if (on) - keyspan_pda_set_modem_info(serial, (1 << 7) | (1 << 2)); + keyspan_pda_set_modem_info(serial, BIT(7) | BIT(2)); else keyspan_pda_set_modem_info(serial, 0); } From 66c32e4833559d8683879b5fa6fe70332db53aec Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Sun, 25 Oct 2020 18:46:00 +0100 Subject: [PATCH 035/172] USB: serial: keyspan_pda: drop redundant usb-serial pointer Drop the redundant struct usb_serial pointer from the driver port data. Acked-by: Sebastian Andrzej Siewior Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/keyspan_pda.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index f582e0a3ae56..e6f933e8d25f 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -76,7 +76,7 @@ static const struct usb_device_id id_table_fake[] = { static int keyspan_pda_get_write_room(struct keyspan_pda_private *priv) { struct usb_serial_port *port = priv->port; - struct usb_serial *serial = priv->serial; + struct usb_serial *serial = port->serial; u8 *room; int rc; @@ -114,7 +114,7 @@ static void keyspan_pda_request_unthrottle(struct work_struct *work) struct keyspan_pda_private *priv = container_of(work, struct keyspan_pda_private, unthrottle_work); struct usb_serial_port *port = priv->port; - struct usb_serial *serial = priv->serial; + struct usb_serial *serial = port->serial; unsigned long flags; int result; @@ -665,7 +665,6 @@ static int keyspan_pda_port_probe(struct usb_serial_port *port) return -ENOMEM; INIT_WORK(&priv->unthrottle_work, keyspan_pda_request_unthrottle); - priv->serial = port->serial; priv->port = port; usb_set_serial_port_data(port, priv); From 5098e77962e7c8947f87bd8c5869c83e000a522a Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 26 Oct 2020 11:43:06 +0100 Subject: [PATCH 036/172] USB: serial: digi_acceleport: fix write-wakeup deadlocks The driver must not call tty_wakeup() while holding its private lock as line disciplines are allowed to call back into write() from write_wakeup(), leading to a deadlock. Also remove the unneeded work struct that was used to defer wakeup in order to work around a possible race in ancient times (see comment about n_tty write_chan() in commit 14b54e39b412 ("USB: serial: remove changelogs and old todo entries")). Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 45 ++++++++-------------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 016e7dec3196..968e08cdcb09 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -198,14 +197,12 @@ struct digi_port { int dp_throttle_restart; wait_queue_head_t dp_flush_wait; wait_queue_head_t dp_close_wait; /* wait queue for close */ - struct work_struct dp_wakeup_work; struct usb_serial_port *dp_port; }; /* Local Function Declarations */ -static void digi_wakeup_write_lock(struct work_struct *work); static int digi_write_oob_command(struct usb_serial_port *port, unsigned char *buf, int count, int interruptible); static int digi_write_inb_command(struct usb_serial_port *port, @@ -356,26 +353,6 @@ __releases(lock) return timeout; } - -/* - * Digi Wakeup Write - * - * Wake up port, line discipline, and tty processes sleeping - * on writes. - */ - -static void digi_wakeup_write_lock(struct work_struct *work) -{ - struct digi_port *priv = - container_of(work, struct digi_port, dp_wakeup_work); - struct usb_serial_port *port = priv->dp_port; - unsigned long flags; - - spin_lock_irqsave(&priv->dp_port_lock, flags); - tty_port_tty_wakeup(&port->port); - spin_unlock_irqrestore(&priv->dp_port_lock, flags); -} - /* * Digi Write OOB Command * @@ -985,6 +962,7 @@ static void digi_write_bulk_callback(struct urb *urb) unsigned long flags; int ret = 0; int status = urb->status; + bool wakeup; /* port and serial sanity check */ if (port == NULL || (priv = usb_get_serial_port_data(port)) == NULL) { @@ -1011,6 +989,7 @@ static void digi_write_bulk_callback(struct urb *urb) } /* try to send any buffered data on this port */ + wakeup = true; spin_lock_irqsave(&priv->dp_port_lock, flags); priv->dp_write_urb_in_use = 0; if (priv->dp_out_buf_len > 0) { @@ -1026,19 +1005,18 @@ static void digi_write_bulk_callback(struct urb *urb) if (ret == 0) { priv->dp_write_urb_in_use = 1; priv->dp_out_buf_len = 0; + wakeup = false; } } - /* wake up processes sleeping on writes immediately */ - tty_port_tty_wakeup(&port->port); - /* also queue up a wakeup at scheduler time, in case we */ - /* lost the race in write_chan(). */ - schedule_work(&priv->dp_wakeup_work); - spin_unlock_irqrestore(&priv->dp_port_lock, flags); + if (ret && ret != -EPERM) dev_err_console(port, "%s: usb_submit_urb failed, ret=%d, port=%d\n", __func__, ret, priv->dp_port_num); + + if (wakeup) + tty_port_tty_wakeup(&port->port); } static int digi_write_room(struct tty_struct *tty) @@ -1238,7 +1216,6 @@ static int digi_port_init(struct usb_serial_port *port, unsigned port_num) init_waitqueue_head(&priv->dp_transmit_idle_wait); init_waitqueue_head(&priv->dp_flush_wait); init_waitqueue_head(&priv->dp_close_wait); - INIT_WORK(&priv->dp_wakeup_work, digi_wakeup_write_lock); priv->dp_port = port; init_waitqueue_head(&port->write_wait); @@ -1507,13 +1484,14 @@ static int digi_read_oob_callback(struct urb *urb) rts = C_CRTSCTS(tty); if (tty && opcode == DIGI_CMD_READ_INPUT_SIGNALS) { + bool wakeup = false; + spin_lock_irqsave(&priv->dp_port_lock, flags); /* convert from digi flags to termiox flags */ if (val & DIGI_READ_INPUT_SIGNALS_CTS) { priv->dp_modem_signals |= TIOCM_CTS; - /* port must be open to use tty struct */ if (rts) - tty_port_tty_wakeup(&port->port); + wakeup = true; } else { priv->dp_modem_signals &= ~TIOCM_CTS; /* port must be open to use tty struct */ @@ -1532,6 +1510,9 @@ static int digi_read_oob_callback(struct urb *urb) priv->dp_modem_signals &= ~TIOCM_CD; spin_unlock_irqrestore(&priv->dp_port_lock, flags); + + if (wakeup) + tty_port_tty_wakeup(&port->port); } else if (opcode == DIGI_CMD_TRANSMIT_IDLE) { spin_lock_irqsave(&priv->dp_port_lock, flags); priv->dp_transmit_idle = 1; From 179dfb954790410f65605f1c479c029c2cd73c55 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 26 Oct 2020 11:44:21 +0100 Subject: [PATCH 037/172] USB: serial: remove write wait queue The digi_acceleport driver is the only driver still using the port write wake queue so move it to that driver's port data. Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 12 ++++++------ include/linux/usb/serial.h | 2 -- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 968e08cdcb09..0ecd5316d85f 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -197,6 +197,7 @@ struct digi_port { int dp_throttle_restart; wait_queue_head_t dp_flush_wait; wait_queue_head_t dp_close_wait; /* wait queue for close */ + wait_queue_head_t write_wait; struct usb_serial_port *dp_port; }; @@ -381,7 +382,7 @@ static int digi_write_oob_command(struct usb_serial_port *port, while (count > 0) { while (oob_priv->dp_write_urb_in_use) { cond_wait_interruptible_timeout_irqrestore( - &oob_port->write_wait, DIGI_RETRY_TIMEOUT, + &oob_priv->write_wait, DIGI_RETRY_TIMEOUT, &oob_priv->dp_port_lock, flags); if (interruptible && signal_pending(current)) return -EINTR; @@ -444,7 +445,7 @@ static int digi_write_inb_command(struct usb_serial_port *port, while (priv->dp_write_urb_in_use && time_before(jiffies, timeout)) { cond_wait_interruptible_timeout_irqrestore( - &port->write_wait, DIGI_RETRY_TIMEOUT, + &priv->write_wait, DIGI_RETRY_TIMEOUT, &priv->dp_port_lock, flags); if (signal_pending(current)) return -EINTR; @@ -523,7 +524,7 @@ static int digi_set_modem_signals(struct usb_serial_port *port, while (oob_priv->dp_write_urb_in_use) { spin_unlock(&port_priv->dp_port_lock); cond_wait_interruptible_timeout_irqrestore( - &oob_port->write_wait, DIGI_RETRY_TIMEOUT, + &oob_priv->write_wait, DIGI_RETRY_TIMEOUT, &oob_priv->dp_port_lock, flags); if (interruptible && signal_pending(current)) return -EINTR; @@ -983,7 +984,7 @@ static void digi_write_bulk_callback(struct urb *urb) dev_dbg(&port->dev, "digi_write_bulk_callback: oob callback\n"); spin_lock_irqsave(&priv->dp_port_lock, flags); priv->dp_write_urb_in_use = 0; - wake_up_interruptible(&port->write_wait); + wake_up_interruptible(&priv->write_wait); spin_unlock_irqrestore(&priv->dp_port_lock, flags); return; } @@ -1216,10 +1217,9 @@ static int digi_port_init(struct usb_serial_port *port, unsigned port_num) init_waitqueue_head(&priv->dp_transmit_idle_wait); init_waitqueue_head(&priv->dp_flush_wait); init_waitqueue_head(&priv->dp_close_wait); + init_waitqueue_head(&priv->write_wait); priv->dp_port = port; - init_waitqueue_head(&port->write_wait); - usb_set_serial_port_data(port, priv); return 0; diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h index 8e67eff9039f..1c09b922f8b0 100644 --- a/include/linux/usb/serial.h +++ b/include/linux/usb/serial.h @@ -62,7 +62,6 @@ * @bulk_out_endpointAddress: endpoint address for the bulk out pipe for this * port. * @flags: usb serial port flags - * @write_wait: a wait_queue_head_t used by the port. * @work: work queue entry for the line discipline waking up. * @dev: pointer to the serial device * @@ -108,7 +107,6 @@ struct usb_serial_port { int tx_bytes; unsigned long flags; - wait_queue_head_t write_wait; struct work_struct work; unsigned long sysrq; /* sysrq timeout */ struct device dev; From 975323ab8f116667676c30ca3502a6757bd89e8d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 4 Nov 2020 17:47:27 +0100 Subject: [PATCH 038/172] USB: serial: mos7720: fix parallel-port state restore The parallel-port restore operations is called when a driver claims the port and is supposed to restore the provided state (e.g. saved when releasing the port). Fixes: b69578df7e98 ("USB: usbserial: mos7720: add support for parallel port on moschip 7715") Cc: stable # 2.6.35 Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/mos7720.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/serial/mos7720.c b/drivers/usb/serial/mos7720.c index 5eed1078fac8..5a5d2a95070e 100644 --- a/drivers/usb/serial/mos7720.c +++ b/drivers/usb/serial/mos7720.c @@ -639,6 +639,8 @@ static void parport_mos7715_restore_state(struct parport *pp, spin_unlock(&release_lock); return; } + mos_parport->shadowDCR = s->u.pc.ctr; + mos_parport->shadowECR = s->u.pc.ecr; write_parport_reg_nonblock(mos_parport, MOS7720_DCR, mos_parport->shadowDCR); write_parport_reg_nonblock(mos_parport, MOS7720_ECR, From 47ea2929d58c35598e681212311d35b240c373ce Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Fri, 9 Oct 2020 16:40:46 +0200 Subject: [PATCH 039/172] usb: typec: ucsi: acpi: Always decode connector change information Normal commands may be reporting that a connector has changed. Always call the usci_connector_change handler and let it take care of scheduling the work when needed. Doing this makes the ACPI code path identical to the CCG one. Cc: Hans de Goede Cc: Heikki Krogerus Acked-by: Heikki Krogerus Signed-off-by: Benjamin Berg Link: https://lore.kernel.org/r/20201009144047.505957-2-benjamin@sipsolutions.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_acpi.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c index fbfe8f5933af..04976435ad73 100644 --- a/drivers/usb/typec/ucsi/ucsi_acpi.c +++ b/drivers/usb/typec/ucsi/ucsi_acpi.c @@ -103,11 +103,12 @@ static void ucsi_acpi_notify(acpi_handle handle, u32 event, void *data) if (ret) return; + if (UCSI_CCI_CONNECTOR(cci)) + ucsi_connector_change(ua->ucsi, UCSI_CCI_CONNECTOR(cci)); + if (test_bit(COMMAND_PENDING, &ua->flags) && cci & (UCSI_CCI_ACK_COMPLETE | UCSI_CCI_COMMAND_COMPLETE)) complete(&ua->complete); - else if (UCSI_CCI_CONNECTOR(cci)) - ucsi_connector_change(ua->ucsi, UCSI_CCI_CONNECTOR(cci)); } static int ucsi_acpi_probe(struct platform_device *pdev) From 217504a055325fe76ec1142aa15f14d3db77f94f Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Fri, 9 Oct 2020 16:40:47 +0200 Subject: [PATCH 040/172] usb: typec: ucsi: Work around PPM losing change information Some/many PPMs are simply clearing the change bitfield when a notification on a port is acknowledge. Unfortunately, doing so means that any changes between the GET_CONNECTOR_STATUS and ACK_CC_CI commands is simply lost. Work around this by re-fetching the connector status afterwards. We can then infer any changes that we see have happened but that may not be respresented in the change bitfield. We end up with the following actions: 1. UCSI_GET_CONNECTOR_STATUS, store result, update unprocessed_changes 2. UCSI_GET_CAM_SUPPORTED, discard result 3. ACK connector change 4. UCSI_GET_CONNECTOR_STATUS, store result 5. Infere lost changes by comparing UCSI_GET_CONNECTOR_STATUS results 6. If PPM reported a new change, then restart in order to ACK 7. Process everything as usual. The worker is also changed to re-schedule itself if a new change notification happened while it was running. Doing this fixes quite commonly occurring issues where e.g. the UCSI power supply would remain online even thought the ThunderBolt cable was unplugged. Cc: Hans de Goede Cc: Heikki Krogerus Acked-by: Heikki Krogerus Signed-off-by: Benjamin Berg Link: https://lore.kernel.org/r/20201009144047.505957-3-benjamin@sipsolutions.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 125 ++++++++++++++++++++++++++++------ drivers/usb/typec/ucsi/ucsi.h | 2 + 2 files changed, 107 insertions(+), 20 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 758b988ac518..fad8680be7ab 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -53,7 +53,7 @@ static int ucsi_acknowledge_connector_change(struct ucsi *ucsi) ctrl = UCSI_ACK_CC_CI; ctrl |= UCSI_ACK_CONNECTOR_CHANGE; - return ucsi->ops->async_write(ucsi, UCSI_CONTROL, &ctrl, sizeof(ctrl)); + return ucsi->ops->sync_write(ucsi, UCSI_CONTROL, &ctrl, sizeof(ctrl)); } static int ucsi_exec_command(struct ucsi *ucsi, u64 command); @@ -625,20 +625,112 @@ static void ucsi_handle_connector_change(struct work_struct *work) struct ucsi_connector *con = container_of(work, struct ucsi_connector, work); struct ucsi *ucsi = con->ucsi; + struct ucsi_connector_status pre_ack_status; + struct ucsi_connector_status post_ack_status; enum typec_role role; + u16 inferred_changes; + u16 changed_flags; u64 command; int ret; mutex_lock(&con->lock); + /* + * Some/many PPMs have an issue where all fields in the change bitfield + * are cleared when an ACK is send. This will causes any change + * between GET_CONNECTOR_STATUS and ACK to be lost. + * + * We work around this by re-fetching the connector status afterwards. + * We then infer any changes that we see have happened but that may not + * be represented in the change bitfield. + * + * Also, even though we don't need to know the currently supported alt + * modes, we run the GET_CAM_SUPPORTED command to ensure the PPM does + * not get stuck in case it assumes we do. + * Always do this, rather than relying on UCSI_CONSTAT_CAM_CHANGE to be + * set in the change bitfield. + * + * We end up with the following actions: + * 1. UCSI_GET_CONNECTOR_STATUS, store result, update unprocessed_changes + * 2. UCSI_GET_CAM_SUPPORTED, discard result + * 3. ACK connector change + * 4. UCSI_GET_CONNECTOR_STATUS, store result + * 5. Infere lost changes by comparing UCSI_GET_CONNECTOR_STATUS results + * 6. If PPM reported a new change, then restart in order to ACK + * 7. Process everything as usual. + * + * We may end up seeing a change twice, but we can only miss extremely + * short transitional changes. + */ + + /* 1. First UCSI_GET_CONNECTOR_STATUS */ command = UCSI_GET_CONNECTOR_STATUS | UCSI_CONNECTOR_NUMBER(con->num); - ret = ucsi_send_command(ucsi, command, &con->status, - sizeof(con->status)); + ret = ucsi_send_command(ucsi, command, &pre_ack_status, + sizeof(pre_ack_status)); if (ret < 0) { dev_err(ucsi->dev, "%s: GET_CONNECTOR_STATUS failed (%d)\n", __func__, ret); goto out_unlock; } + con->unprocessed_changes |= pre_ack_status.change; + + /* 2. Run UCSI_GET_CAM_SUPPORTED and discard the result. */ + command = UCSI_GET_CAM_SUPPORTED; + command |= UCSI_CONNECTOR_NUMBER(con->num); + ucsi_send_command(con->ucsi, command, NULL, 0); + + /* 3. ACK connector change */ + clear_bit(EVENT_PENDING, &ucsi->flags); + ret = ucsi_acknowledge_connector_change(ucsi); + if (ret) { + dev_err(ucsi->dev, "%s: ACK failed (%d)", __func__, ret); + goto out_unlock; + } + + /* 4. Second UCSI_GET_CONNECTOR_STATUS */ + command = UCSI_GET_CONNECTOR_STATUS | UCSI_CONNECTOR_NUMBER(con->num); + ret = ucsi_send_command(ucsi, command, &post_ack_status, + sizeof(post_ack_status)); + if (ret < 0) { + dev_err(ucsi->dev, "%s: GET_CONNECTOR_STATUS failed (%d)\n", + __func__, ret); + goto out_unlock; + } + + /* 5. Inferre any missing changes */ + changed_flags = pre_ack_status.flags ^ post_ack_status.flags; + inferred_changes = 0; + if (UCSI_CONSTAT_PWR_OPMODE(changed_flags) != 0) + inferred_changes |= UCSI_CONSTAT_POWER_OPMODE_CHANGE; + + if (changed_flags & UCSI_CONSTAT_CONNECTED) + inferred_changes |= UCSI_CONSTAT_CONNECT_CHANGE; + + if (changed_flags & UCSI_CONSTAT_PWR_DIR) + inferred_changes |= UCSI_CONSTAT_POWER_DIR_CHANGE; + + if (UCSI_CONSTAT_PARTNER_FLAGS(changed_flags) != 0) + inferred_changes |= UCSI_CONSTAT_PARTNER_CHANGE; + + if (UCSI_CONSTAT_PARTNER_TYPE(changed_flags) != 0) + inferred_changes |= UCSI_CONSTAT_PARTNER_CHANGE; + + /* Mask out anything that was correctly notified in the later call. */ + inferred_changes &= ~post_ack_status.change; + if (inferred_changes) + dev_dbg(ucsi->dev, "%s: Inferred changes that would have been lost: 0x%04x\n", + __func__, inferred_changes); + + con->unprocessed_changes |= inferred_changes; + + /* 6. If PPM reported a new change, then restart in order to ACK */ + if (post_ack_status.change) + goto out_unlock; + + /* 7. Continue as if nothing happened */ + con->status = post_ack_status; + con->status.change = con->unprocessed_changes; + con->unprocessed_changes = 0; role = !!(con->status.flags & UCSI_CONSTAT_PWR_DIR); @@ -676,28 +768,19 @@ static void ucsi_handle_connector_change(struct work_struct *work) ucsi_unregister_partner(con); } - if (con->status.change & UCSI_CONSTAT_CAM_CHANGE) { - /* - * We don't need to know the currently supported alt modes here. - * Running GET_CAM_SUPPORTED command just to make sure the PPM - * does not get stuck in case it assumes we do so. - */ - command = UCSI_GET_CAM_SUPPORTED; - command |= UCSI_CONNECTOR_NUMBER(con->num); - ucsi_send_command(con->ucsi, command, NULL, 0); - } - if (con->status.change & UCSI_CONSTAT_PARTNER_CHANGE) ucsi_partner_change(con); - ret = ucsi_acknowledge_connector_change(ucsi); - if (ret) - dev_err(ucsi->dev, "%s: ACK failed (%d)", __func__, ret); - trace_ucsi_connector_change(con->num, &con->status); out_unlock: - clear_bit(EVENT_PENDING, &ucsi->flags); + if (test_and_clear_bit(EVENT_PENDING, &ucsi->flags)) { + schedule_work(&con->work); + mutex_unlock(&con->lock); + return; + } + + clear_bit(EVENT_PROCESSING, &ucsi->flags); mutex_unlock(&con->lock); } @@ -715,7 +798,9 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num) return; } - if (!test_and_set_bit(EVENT_PENDING, &ucsi->flags)) + set_bit(EVENT_PENDING, &ucsi->flags); + + if (!test_and_set_bit(EVENT_PROCESSING, &ucsi->flags)) schedule_work(&con->work); } EXPORT_SYMBOL_GPL(ucsi_connector_change); diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index cba6f77bea61..543d54a33cb9 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -296,6 +296,7 @@ struct ucsi { #define EVENT_PENDING 0 #define COMMAND_PENDING 1 #define ACK_PENDING 2 +#define EVENT_PROCESSING 3 }; #define UCSI_MAX_SVID 5 @@ -322,6 +323,7 @@ struct ucsi_connector { struct typec_capability typec_cap; + u16 unprocessed_changes; struct ucsi_connector_status status; struct ucsi_connector_capability cap; struct power_supply *psy; From 9e39aef3a105d3c934d9348cc27f1ccfd9ebee01 Mon Sep 17 00:00:00 2001 From: Zou Wei Date: Fri, 30 Oct 2020 17:28:17 +0800 Subject: [PATCH 041/172] usb: misc: brcmstb-usb-pinmap: Make sync_all_pins static Fix the following sparse warning: drivers/usb/misc/brcmstb-usb-pinmap.c:219:6: warning: symbol 'sync_all_pins' was not declared. Should it be static? The sync_all_pins has only call site within brcmstb-usb-pinmap.c It should be static Fixes: 517c4c44b323 ("usb: Add driver to allow any GPIO to be used for 7211 USB signals") Reported-by: Hulk Robot Signed-off-by: Zou Wei Link: https://lore.kernel.org/r/1604050097-91302-1-git-send-email-zou_wei@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/brcmstb-usb-pinmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/misc/brcmstb-usb-pinmap.c b/drivers/usb/misc/brcmstb-usb-pinmap.c index 2326e60545f7..b3cfe8666ea7 100644 --- a/drivers/usb/misc/brcmstb-usb-pinmap.c +++ b/drivers/usb/misc/brcmstb-usb-pinmap.c @@ -216,7 +216,7 @@ static int parse_pins(struct device *dev, struct device_node *dn, return 0; } -void sync_all_pins(struct brcmstb_usb_pinmap_data *pdata) +static void sync_all_pins(struct brcmstb_usb_pinmap_data *pdata) { struct out_pin *pout; struct in_pin *pin; From 81816f5048bac5f4b202ed1443e9788dfc31a18c Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 7 Oct 2020 17:08:47 +0300 Subject: [PATCH 042/172] thunderbolt: Do not clear USB4 router protocol adapter IFC and ISE bits These fields are marked as vendor defined in the USB4 spec and should not be modified by the software, so only clear them when we are dealing with pre-USB4 hardware. Signed-off-by: Mika Westerberg Acked-by: Yehezkel Bernat Reviewed-by: Greg Kroah-Hartman --- drivers/thunderbolt/path.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c index 03e7b714deab..7c2c45d9ba4a 100644 --- a/drivers/thunderbolt/path.c +++ b/drivers/thunderbolt/path.c @@ -406,10 +406,17 @@ static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index, if (!hop.pending) { if (clear_fc) { - /* Clear flow control */ - hop.ingress_fc = 0; + /* + * Clear flow control. Protocol adapters + * IFC and ISE bits are vendor defined + * in the USB4 spec so we clear them + * only for pre-USB4 adapters. + */ + if (!tb_switch_is_usb4(port->sw)) { + hop.ingress_fc = 0; + hop.ingress_shared_buffer = 0; + } hop.egress_fc = 0; - hop.ingress_shared_buffer = 0; hop.egress_shared_buffer = 0; return tb_port_write(port, &hop, TB_CFG_HOPS, From d67274bacb8a2bc2a6cfd2a0cef4963379e3eb26 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 7 Oct 2020 17:01:43 +0300 Subject: [PATCH 043/172] thunderbolt: Find XDomain by route instead of UUID We are going to represent loops back to the host also as XDomains and they all have the same (host) UUID, so finding them needs to use route string instead. This also requires that we check if the XDomain device is added to the bus before its properties can be updated. Otherwise the remote UUID might not be populated yet. Signed-off-by: Mika Westerberg Acked-by: Yehezkel Bernat Reviewed-by: Greg Kroah-Hartman --- drivers/thunderbolt/xdomain.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 48907853732a..e436e9efa7e7 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -587,8 +587,6 @@ static void tb_xdp_handle_request(struct work_struct *work) break; case PROPERTIES_CHANGED_REQUEST: { - const struct tb_xdp_properties_changed *xchg = - (const struct tb_xdp_properties_changed *)pkg; struct tb_xdomain *xd; ret = tb_xdp_properties_changed_response(ctl, route, sequence); @@ -598,10 +596,12 @@ static void tb_xdp_handle_request(struct work_struct *work) * the xdomain related to this connection as well in * case there is a change in services it offers. */ - xd = tb_xdomain_find_by_uuid_locked(tb, &xchg->src_uuid); + xd = tb_xdomain_find_by_route_locked(tb, route); if (xd) { - queue_delayed_work(tb->wq, &xd->get_properties_work, - msecs_to_jiffies(50)); + if (device_is_registered(&xd->dev)) { + queue_delayed_work(tb->wq, &xd->get_properties_work, + msecs_to_jiffies(50)); + } tb_xdomain_put(xd); } From 47844ecb8cec2916abaad9c1d1b801f49ed6265d Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 7 Oct 2020 18:19:28 +0300 Subject: [PATCH 044/172] thunderbolt: Create XDomain devices for loops back to the host It is perfectly possible to have loops back from the routers to the host, or even from one host port to another. Instead of ignoring these, we create XDomain devices for each. This allows creating services such as DMA traffic test that is used in manufacturing for example. Signed-off-by: Mika Westerberg Acked-by: Yehezkel Bernat Reviewed-by: Greg Kroah-Hartman --- drivers/thunderbolt/xdomain.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index e436e9efa7e7..da229ac4e471 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -961,10 +961,8 @@ static void tb_xdomain_get_uuid(struct work_struct *work) return; } - if (uuid_equal(&uuid, xd->local_uuid)) { + if (uuid_equal(&uuid, xd->local_uuid)) dev_dbg(&xd->dev, "intra-domain loop detected\n"); - return; - } /* * If the UUID is different, there is another domain connected From 4210d50f0b3e423e10a7a254b2a67f5c5318868e Mon Sep 17 00:00:00 2001 From: Isaac Hazan Date: Thu, 24 Sep 2020 11:43:58 +0300 Subject: [PATCH 045/172] thunderbolt: Add link_speed and link_width to XDomain Link speed and link width are needed for checking expected values in case of using a loopback service. Signed-off-by: Isaac Hazan Signed-off-by: Mika Westerberg Acked-by: Yehezkel Bernat Reviewed-by: Greg Kroah-Hartman --- .../ABI/testing/sysfs-bus-thunderbolt | 28 ++++++++ drivers/thunderbolt/switch.c | 9 ++- drivers/thunderbolt/tb.h | 1 + drivers/thunderbolt/xdomain.c | 65 +++++++++++++++++++ include/linux/thunderbolt.h | 4 ++ 5 files changed, 106 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-bus-thunderbolt b/Documentation/ABI/testing/sysfs-bus-thunderbolt index 0b4ab9e4b8f4..a91b4b24496e 100644 --- a/Documentation/ABI/testing/sysfs-bus-thunderbolt +++ b/Documentation/ABI/testing/sysfs-bus-thunderbolt @@ -1,3 +1,31 @@ +What: /sys/bus/thunderbolt/devices//rx_speed +Date: Feb 2021 +KernelVersion: 5.11 +Contact: Isaac Hazan +Description: This attribute reports the XDomain RX speed per lane. + All RX lanes run at the same speed. + +What: /sys/bus/thunderbolt/devices//rx_lanes +Date: Feb 2021 +KernelVersion: 5.11 +Contact: Isaac Hazan +Description: This attribute reports the number of RX lanes the XDomain + is using simultaneously through its upstream port. + +What: /sys/bus/thunderbolt/devices//tx_speed +Date: Feb 2021 +KernelVersion: 5.11 +Contact: Isaac Hazan +Description: This attribute reports the XDomain TX speed per lane. + All TX lanes run at the same speed. + +What: /sys/bus/thunderbolt/devices//tx_lanes +Date: Feb 2021 +KernelVersion: 5.11 +Contact: Isaac Hazan +Description: This attribute reports number of TX lanes the XDomain + is using simultaneously through its upstream port. + What: /sys/bus/thunderbolt/devices/.../domainX/boot_acl Date: Jun 2018 KernelVersion: 4.17 diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index c73bbfe69ba1..05a360901790 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -932,7 +932,14 @@ int tb_port_get_link_speed(struct tb_port *port) return speed == LANE_ADP_CS_1_CURRENT_SPEED_GEN3 ? 20 : 10; } -static int tb_port_get_link_width(struct tb_port *port) +/** + * tb_port_get_link_width() - Get current link width + * @port: Port to check (USB4 or CIO) + * + * Returns link width. Return values can be 1 (Single-Lane), 2 (Dual-Lane) + * or negative errno in case of failure. + */ +int tb_port_get_link_width(struct tb_port *port) { u32 val; int ret; diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index a9995e21b916..3a826315049e 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -862,6 +862,7 @@ struct tb_port *tb_next_port_on_path(struct tb_port *start, struct tb_port *end, (p) = tb_next_port_on_path((src), (dst), (p))) int tb_port_get_link_speed(struct tb_port *port); +int tb_port_get_link_width(struct tb_port *port); int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec); int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap); diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index da229ac4e471..83a315f96934 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -942,6 +942,43 @@ static void tb_xdomain_restore_paths(struct tb_xdomain *xd) } } +static inline struct tb_switch *tb_xdomain_parent(struct tb_xdomain *xd) +{ + return tb_to_switch(xd->dev.parent); +} + +static int tb_xdomain_update_link_attributes(struct tb_xdomain *xd) +{ + bool change = false; + struct tb_port *port; + int ret; + + port = tb_port_at(xd->route, tb_xdomain_parent(xd)); + + ret = tb_port_get_link_speed(port); + if (ret < 0) + return ret; + + if (xd->link_speed != ret) + change = true; + + xd->link_speed = ret; + + ret = tb_port_get_link_width(port); + if (ret < 0) + return ret; + + if (xd->link_width != ret) + change = true; + + xd->link_width = ret; + + if (change) + kobject_uevent(&xd->dev.kobj, KOBJ_CHANGE); + + return 0; +} + static void tb_xdomain_get_uuid(struct work_struct *work) { struct tb_xdomain *xd = container_of(work, typeof(*xd), @@ -1053,6 +1090,8 @@ static void tb_xdomain_get_properties(struct work_struct *work) xd->properties = dir; xd->property_block_gen = gen; + tb_xdomain_update_link_attributes(xd); + tb_xdomain_restore_paths(xd); mutex_unlock(&xd->lock); @@ -1159,9 +1198,35 @@ static ssize_t unique_id_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RO(unique_id); +static ssize_t speed_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, dev); + + return sprintf(buf, "%u.0 Gb/s\n", xd->link_speed); +} + +static DEVICE_ATTR(rx_speed, 0444, speed_show, NULL); +static DEVICE_ATTR(tx_speed, 0444, speed_show, NULL); + +static ssize_t lanes_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, dev); + + return sprintf(buf, "%u\n", xd->link_width); +} + +static DEVICE_ATTR(rx_lanes, 0444, lanes_show, NULL); +static DEVICE_ATTR(tx_lanes, 0444, lanes_show, NULL); + static struct attribute *xdomain_attrs[] = { &dev_attr_device.attr, &dev_attr_device_name.attr, + &dev_attr_rx_lanes.attr, + &dev_attr_rx_speed.attr, + &dev_attr_tx_lanes.attr, + &dev_attr_tx_speed.attr, &dev_attr_unique_id.attr, &dev_attr_vendor.attr, &dev_attr_vendor_name.attr, diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 5db2b11ab085..e441af88ed77 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -179,6 +179,8 @@ void tb_unregister_property_dir(const char *key, struct tb_property_dir *dir); * @lock: Lock to serialize access to the following fields of this structure * @vendor_name: Name of the vendor (or %NULL if not known) * @device_name: Name of the device (or %NULL if not known) + * @link_speed: Speed of the link in Gb/s + * @link_width: Width of the link (1 or 2) * @is_unplugged: The XDomain is unplugged * @resume: The XDomain is being resumed * @needs_uuid: If the XDomain does not have @remote_uuid it will be @@ -223,6 +225,8 @@ struct tb_xdomain { struct mutex lock; const char *vendor_name; const char *device_name; + unsigned int link_speed; + unsigned int link_width; bool is_unplugged; bool resume; bool needs_uuid; From 5cc0df9ce10a860aaeac53f8df1cc8754c5c7b03 Mon Sep 17 00:00:00 2001 From: Isaac Hazan Date: Thu, 24 Sep 2020 11:44:01 +0300 Subject: [PATCH 046/172] thunderbolt: Add functions for enabling and disabling lane bonding on XDomain These can be used by service drivers to enable and disable lane bonding as needed. Signed-off-by: Isaac Hazan Signed-off-by: Mika Westerberg Acked-by: Yehezkel Bernat Reviewed-by: Greg Kroah-Hartman --- drivers/thunderbolt/switch.c | 24 +++++++++++-- drivers/thunderbolt/tb.h | 3 ++ drivers/thunderbolt/xdomain.c | 66 +++++++++++++++++++++++++++++++++++ include/linux/thunderbolt.h | 2 ++ 4 files changed, 92 insertions(+), 3 deletions(-) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 05a360901790..cdfd8cccfe19 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -503,12 +503,13 @@ static void tb_dump_port(struct tb *tb, struct tb_regs_port_header *port) /** * tb_port_state() - get connectedness state of a port + * @port: the port to check * * The port must have a TB_CAP_PHY (i.e. it should be a real port). * * Return: Returns an enum tb_port_state on success or an error code on failure. */ -static int tb_port_state(struct tb_port *port) +int tb_port_state(struct tb_port *port) { struct tb_cap_phy phy; int res; @@ -1008,7 +1009,16 @@ static int tb_port_set_link_width(struct tb_port *port, unsigned int width) port->cap_phy + LANE_ADP_CS_1, 1); } -static int tb_port_lane_bonding_enable(struct tb_port *port) +/** + * tb_port_lane_bonding_enable() - Enable bonding on port + * @port: port to enable + * + * Enable bonding by setting the link width of the port and the + * other port in case of dual link port. + * + * Return: %0 in case of success and negative errno in case of error + */ +int tb_port_lane_bonding_enable(struct tb_port *port) { int ret; @@ -1038,7 +1048,15 @@ static int tb_port_lane_bonding_enable(struct tb_port *port) return 0; } -static void tb_port_lane_bonding_disable(struct tb_port *port) +/** + * tb_port_lane_bonding_disable() - Disable bonding on port + * @port: port to disable + * + * Disable bonding by setting the link width of the port and the + * other port in case of dual link port. + * + */ +void tb_port_lane_bonding_disable(struct tb_port *port) { port->dual_link_port->bonded = false; port->bonded = false; diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 3a826315049e..e98d3561648d 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -863,6 +863,9 @@ struct tb_port *tb_next_port_on_path(struct tb_port *start, struct tb_port *end, int tb_port_get_link_speed(struct tb_port *port); int tb_port_get_link_width(struct tb_port *port); +int tb_port_state(struct tb_port *port); +int tb_port_lane_bonding_enable(struct tb_port *port); +void tb_port_lane_bonding_disable(struct tb_port *port); int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec); int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap); diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 83a315f96934..65108216bfe3 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -21,6 +22,7 @@ #define XDOMAIN_UUID_RETRIES 10 #define XDOMAIN_PROPERTIES_RETRIES 60 #define XDOMAIN_PROPERTIES_CHANGED_RETRIES 10 +#define XDOMAIN_BONDING_WAIT 100 /* ms */ struct xdomain_request_work { struct work_struct work; @@ -1443,6 +1445,70 @@ void tb_xdomain_remove(struct tb_xdomain *xd) device_unregister(&xd->dev); } +/** + * tb_xdomain_lane_bonding_enable() - Enable lane bonding on XDomain + * @xd: XDomain connection + * + * Lane bonding is disabled by default for XDomains. This function tries + * to enable bonding by first enabling the port and waiting for the CL0 + * state. + * + * Return: %0 in case of success and negative errno in case of error. + */ +int tb_xdomain_lane_bonding_enable(struct tb_xdomain *xd) +{ + struct tb_port *port; + int ret; + + port = tb_port_at(xd->route, tb_xdomain_parent(xd)); + if (!port->dual_link_port) + return -ENODEV; + + ret = tb_port_enable(port->dual_link_port); + if (ret) + return ret; + + ret = tb_wait_for_port(port->dual_link_port, true); + if (ret < 0) + return ret; + if (!ret) + return -ENOTCONN; + + ret = tb_port_lane_bonding_enable(port); + if (ret) { + tb_port_warn(port, "failed to enable lane bonding\n"); + return ret; + } + + tb_xdomain_update_link_attributes(xd); + + dev_dbg(&xd->dev, "lane bonding enabled\n"); + return 0; +} +EXPORT_SYMBOL_GPL(tb_xdomain_lane_bonding_enable); + +/** + * tb_xdomain_lane_bonding_disable() - Disable lane bonding + * @xd: XDomain connection + * + * Lane bonding is disabled by default for XDomains. If bonding has been + * enabled, this function can be used to disable it. + */ +void tb_xdomain_lane_bonding_disable(struct tb_xdomain *xd) +{ + struct tb_port *port; + + port = tb_port_at(xd->route, tb_xdomain_parent(xd)); + if (port->dual_link_port) { + tb_port_lane_bonding_disable(port); + tb_port_disable(port->dual_link_port); + tb_xdomain_update_link_attributes(xd); + + dev_dbg(&xd->dev, "lane bonding disabled\n"); + } +} +EXPORT_SYMBOL_GPL(tb_xdomain_lane_bonding_disable); + /** * tb_xdomain_enable_paths() - Enable DMA paths for XDomain connection * @xd: XDomain connection diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index e441af88ed77..0a747f92847e 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -247,6 +247,8 @@ struct tb_xdomain { u8 depth; }; +int tb_xdomain_lane_bonding_enable(struct tb_xdomain *xd); +void tb_xdomain_lane_bonding_disable(struct tb_xdomain *xd); int tb_xdomain_enable_paths(struct tb_xdomain *xd, u16 transmit_path, u16 transmit_ring, u16 receive_path, u16 receive_ring); From 407ac931aefda91ac90498c6b6e6893982173613 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 7 Oct 2020 17:53:44 +0300 Subject: [PATCH 047/172] thunderbolt: Create debugfs directory automatically for services This allows service drivers to use it as parent directory if they need to add their own debugfs entries. Signed-off-by: Mika Westerberg Acked-by: Yehezkel Bernat Reviewed-by: Greg Kroah-Hartman --- drivers/thunderbolt/debugfs.c | 24 ++++++++++++++++++++++++ drivers/thunderbolt/tb.h | 4 ++++ drivers/thunderbolt/xdomain.c | 3 +++ include/linux/thunderbolt.h | 4 ++++ 4 files changed, 35 insertions(+) diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c index 3680b2784ea1..e53ca8270acd 100644 --- a/drivers/thunderbolt/debugfs.c +++ b/drivers/thunderbolt/debugfs.c @@ -690,6 +690,30 @@ void tb_switch_debugfs_remove(struct tb_switch *sw) debugfs_remove_recursive(sw->debugfs_dir); } +/** + * tb_service_debugfs_init() - Add debugfs directory for service + * @svc: Thunderbolt service pointer + * + * Adds debugfs directory for service. + */ +void tb_service_debugfs_init(struct tb_service *svc) +{ + svc->debugfs_dir = debugfs_create_dir(dev_name(&svc->dev), + tb_debugfs_root); +} + +/** + * tb_service_debugfs_remove() - Remove service debugfs directory + * @svc: Thunderbolt service pointer + * + * Removes the previously created debugfs directory for @svc. + */ +void tb_service_debugfs_remove(struct tb_service *svc) +{ + debugfs_remove_recursive(svc->debugfs_dir); + svc->debugfs_dir = NULL; +} + void tb_debugfs_init(void) { tb_debugfs_root = debugfs_create_dir("thunderbolt", NULL); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index e98d3561648d..a21000649009 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -1027,11 +1027,15 @@ void tb_debugfs_init(void); void tb_debugfs_exit(void); void tb_switch_debugfs_init(struct tb_switch *sw); void tb_switch_debugfs_remove(struct tb_switch *sw); +void tb_service_debugfs_init(struct tb_service *svc); +void tb_service_debugfs_remove(struct tb_service *svc); #else static inline void tb_debugfs_init(void) { } static inline void tb_debugfs_exit(void) { } static inline void tb_switch_debugfs_init(struct tb_switch *sw) { } static inline void tb_switch_debugfs_remove(struct tb_switch *sw) { } +static inline void tb_service_debugfs_init(struct tb_service *svc) { } +static inline void tb_service_debugfs_remove(struct tb_service *svc) { } #endif #ifdef CONFIG_USB4_KUNIT_TEST diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 65108216bfe3..1a0491b461fd 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -779,6 +779,7 @@ static void tb_service_release(struct device *dev) struct tb_service *svc = container_of(dev, struct tb_service, dev); struct tb_xdomain *xd = tb_service_parent(svc); + tb_service_debugfs_remove(svc); ida_simple_remove(&xd->service_ids, svc->id); kfree(svc->key); kfree(svc); @@ -892,6 +893,8 @@ static void enumerate_services(struct tb_xdomain *xd) svc->dev.parent = &xd->dev; dev_set_name(&svc->dev, "%s.%d", dev_name(&xd->dev), svc->id); + tb_service_debugfs_init(svc); + if (device_register(&svc->dev)) { put_device(&svc->dev); break; diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 0a747f92847e..a844fd5d96ab 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -350,6 +350,9 @@ void tb_unregister_protocol_handler(struct tb_protocol_handler *handler); * @prtcvers: Protocol version from the properties directory * @prtcrevs: Protocol software revision from the properties directory * @prtcstns: Protocol settings mask from the properties directory + * @debugfs_dir: Pointer to the service debugfs directory. Always created + * when debugfs is enabled. Can be used by service drivers to + * add their own entries under the service. * * Each domain exposes set of services it supports as collection of * properties. For each service there will be one corresponding @@ -363,6 +366,7 @@ struct tb_service { u32 prtcvers; u32 prtcrevs; u32 prtcstns; + struct dentry *debugfs_dir; }; static inline struct tb_service *tb_service_get(struct tb_service *svc) From 5bf722df5d37e82fd252b1d3e37cde4eab355c1c Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 7 Oct 2020 18:17:12 +0300 Subject: [PATCH 048/172] thunderbolt: Make it possible to allocate one directional DMA tunnel With DMA tunnels it is possible that the service using it does not require bi-directional paths so make RX and TX optional (but of course one of them needs to be set). Signed-off-by: Mika Westerberg Acked-by: Yehezkel Bernat Reviewed-by: Greg Kroah-Hartman --- drivers/thunderbolt/tunnel.c | 50 ++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 829b6ccdd5d4..dcdf9c7a9cae 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -34,9 +34,6 @@ #define TB_DP_AUX_PATH_OUT 1 #define TB_DP_AUX_PATH_IN 2 -#define TB_DMA_PATH_OUT 0 -#define TB_DMA_PATH_IN 1 - static const char * const tb_tunnel_names[] = { "PCI", "DP", "DMA", "USB3" }; #define __TB_TUNNEL_PRINT(level, tunnel, fmt, arg...) \ @@ -829,10 +826,10 @@ static void tb_dma_init_path(struct tb_path *path, unsigned int isb, * @nhi: Host controller port * @dst: Destination null port which the other domain is connected to * @transmit_ring: NHI ring number used to send packets towards the - * other domain + * other domain. Set to %0 if TX path is not needed. * @transmit_path: HopID used for transmitting packets * @receive_ring: NHI ring number used to receive packets from the - * other domain + * other domain. Set to %0 if RX path is not needed. * @reveive_path: HopID used for receiving packets * * Return: Returns a tb_tunnel on success or NULL on failure. @@ -843,10 +840,19 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, int receive_path) { struct tb_tunnel *tunnel; + size_t npaths = 0, i = 0; struct tb_path *path; u32 credits; - tunnel = tb_tunnel_alloc(tb, 2, TB_TUNNEL_DMA); + if (receive_ring) + npaths++; + if (transmit_ring) + npaths++; + + if (WARN_ON(!npaths)) + return NULL; + + tunnel = tb_tunnel_alloc(tb, npaths, TB_TUNNEL_DMA); if (!tunnel) return NULL; @@ -856,22 +862,28 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi, credits = tb_dma_credits(nhi); - path = tb_path_alloc(tb, dst, receive_path, nhi, receive_ring, 0, "DMA RX"); - if (!path) { - tb_tunnel_free(tunnel); - return NULL; + if (receive_ring) { + path = tb_path_alloc(tb, dst, receive_path, nhi, receive_ring, 0, + "DMA RX"); + if (!path) { + tb_tunnel_free(tunnel); + return NULL; + } + tb_dma_init_path(path, TB_PATH_NONE, TB_PATH_SOURCE | TB_PATH_INTERNAL, + credits); + tunnel->paths[i++] = path; } - tb_dma_init_path(path, TB_PATH_NONE, TB_PATH_SOURCE | TB_PATH_INTERNAL, - credits); - tunnel->paths[TB_DMA_PATH_IN] = path; - path = tb_path_alloc(tb, nhi, transmit_ring, dst, transmit_path, 0, "DMA TX"); - if (!path) { - tb_tunnel_free(tunnel); - return NULL; + if (transmit_ring) { + path = tb_path_alloc(tb, nhi, transmit_ring, dst, transmit_path, 0, + "DMA TX"); + if (!path) { + tb_tunnel_free(tunnel); + return NULL; + } + tb_dma_init_path(path, TB_PATH_SOURCE, TB_PATH_ALL, credits); + tunnel->paths[i++] = path; } - tb_dma_init_path(path, TB_PATH_SOURCE, TB_PATH_ALL, credits); - tunnel->paths[TB_DMA_PATH_OUT] = path; return tunnel; } From afe704a2d0618ebdb559b5ddb059f6cdbfc78783 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 19 Oct 2020 19:15:20 +0300 Subject: [PATCH 049/172] thunderbolt: Add support for end-to-end flow control USB4 spec defines end-to-end (E2E) flow control that can be used between hosts to prevent overflow of a RX ring. We previously had this partially implemented but that code was removed with commit 53f13319d131 ("thunderbolt: Get rid of E2E workaround") with the idea that we add it back properly if there ever is need. Now that we are going to add DMA traffic test driver (in subsequent patches) this can be useful. For this reason we modify tb_ring_alloc_rx/tx() so that they accept RING_FLAG_E2E and configure the hardware ring accordingly. The RX side also requires passing TX HopID (e2e_tx_hop) used in the credit grant packets. Signed-off-by: Mika Westerberg Acked-by: Yehezkel Bernat Reviewed-by: Greg Kroah-Hartman --- drivers/net/thunderbolt.c | 2 +- drivers/thunderbolt/ctl.c | 4 ++-- drivers/thunderbolt/nhi.c | 36 ++++++++++++++++++++++++++++++++---- include/linux/thunderbolt.h | 8 +++++++- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/drivers/net/thunderbolt.c b/drivers/net/thunderbolt.c index 3160443ef3b9..d7b5f87eaa15 100644 --- a/drivers/net/thunderbolt.c +++ b/drivers/net/thunderbolt.c @@ -866,7 +866,7 @@ static int tbnet_open(struct net_device *dev) eof_mask = BIT(TBIP_PDF_FRAME_END); ring = tb_ring_alloc_rx(xd->tb->nhi, -1, TBNET_RING_SIZE, - RING_FLAG_FRAME, sof_mask, eof_mask, + RING_FLAG_FRAME, 0, sof_mask, eof_mask, tbnet_start_poll, net); if (!ring) { netdev_err(dev, "failed to allocate Rx ring\n"); diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index 9894b8f63064..1d86e27a0ef3 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -628,8 +628,8 @@ struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, event_cb cb, void *cb_data) if (!ctl->tx) goto err; - ctl->rx = tb_ring_alloc_rx(nhi, 0, 10, RING_FLAG_NO_SUSPEND, 0xffff, - 0xffff, NULL, NULL); + ctl->rx = tb_ring_alloc_rx(nhi, 0, 10, RING_FLAG_NO_SUSPEND, 0, 0xffff, + 0xffff, NULL, NULL); if (!ctl->rx) goto err; diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 3f79baa54829..a69bc6b49405 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -483,7 +483,7 @@ err_unlock: static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, bool transmit, unsigned int flags, - u16 sof_mask, u16 eof_mask, + int e2e_tx_hop, u16 sof_mask, u16 eof_mask, void (*start_poll)(void *), void *poll_data) { @@ -506,6 +506,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, ring->is_tx = transmit; ring->size = size; ring->flags = flags; + ring->e2e_tx_hop = e2e_tx_hop; ring->sof_mask = sof_mask; ring->eof_mask = eof_mask; ring->head = 0; @@ -550,7 +551,7 @@ err_free_ring: struct tb_ring *tb_ring_alloc_tx(struct tb_nhi *nhi, int hop, int size, unsigned int flags) { - return tb_ring_alloc(nhi, hop, size, true, flags, 0, 0, NULL, NULL); + return tb_ring_alloc(nhi, hop, size, true, flags, 0, 0, 0, NULL, NULL); } EXPORT_SYMBOL_GPL(tb_ring_alloc_tx); @@ -560,6 +561,7 @@ EXPORT_SYMBOL_GPL(tb_ring_alloc_tx); * @hop: HopID (ring) to allocate. Pass %-1 for automatic allocation. * @size: Number of entries in the ring * @flags: Flags for the ring + * @e2e_tx_hop: Transmit HopID when E2E is enabled in @flags * @sof_mask: Mask of PDF values that start a frame * @eof_mask: Mask of PDF values that end a frame * @start_poll: If not %NULL the ring will call this function when an @@ -568,10 +570,11 @@ EXPORT_SYMBOL_GPL(tb_ring_alloc_tx); * @poll_data: Optional data passed to @start_poll */ struct tb_ring *tb_ring_alloc_rx(struct tb_nhi *nhi, int hop, int size, - unsigned int flags, u16 sof_mask, u16 eof_mask, + unsigned int flags, int e2e_tx_hop, + u16 sof_mask, u16 eof_mask, void (*start_poll)(void *), void *poll_data) { - return tb_ring_alloc(nhi, hop, size, false, flags, sof_mask, eof_mask, + return tb_ring_alloc(nhi, hop, size, false, flags, e2e_tx_hop, sof_mask, eof_mask, start_poll, poll_data); } EXPORT_SYMBOL_GPL(tb_ring_alloc_rx); @@ -618,6 +621,31 @@ void tb_ring_start(struct tb_ring *ring) ring_iowrite32options(ring, sof_eof_mask, 4); ring_iowrite32options(ring, flags, 0); } + + /* + * Now that the ring valid bit is set we can configure E2E if + * enabled for the ring. + */ + if (ring->flags & RING_FLAG_E2E) { + if (!ring->is_tx) { + u32 hop; + + hop = ring->e2e_tx_hop << REG_RX_OPTIONS_E2E_HOP_SHIFT; + hop &= REG_RX_OPTIONS_E2E_HOP_MASK; + flags |= hop; + + dev_dbg(&ring->nhi->pdev->dev, + "enabling E2E for %s %d with TX HopID %d\n", + RING_TYPE(ring), ring->hop, ring->e2e_tx_hop); + } else { + dev_dbg(&ring->nhi->pdev->dev, "enabling E2E for %s %d\n", + RING_TYPE(ring), ring->hop); + } + + flags |= RING_FLAG_E2E_FLOW_CONTROL; + ring_iowrite32options(ring, flags, 0); + } + ring_interrupt_active(ring, true); ring->running = true; err: diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index a844fd5d96ab..034dccf93955 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -481,6 +481,8 @@ struct tb_nhi { * @irq: MSI-X irq number if the ring uses MSI-X. %0 otherwise. * @vector: MSI-X vector number the ring uses (only set if @irq is > 0) * @flags: Ring specific flags + * @e2e_tx_hop: Transmit HopID when E2E is enabled. Only applicable to + * RX ring. For TX ring this should be set to %0. * @sof_mask: Bit mask used to detect start of frame PDF * @eof_mask: Bit mask used to detect end of frame PDF * @start_poll: Called when ring interrupt is triggered to start @@ -504,6 +506,7 @@ struct tb_ring { int irq; u8 vector; unsigned int flags; + int e2e_tx_hop; u16 sof_mask; u16 eof_mask; void (*start_poll)(void *data); @@ -514,6 +517,8 @@ struct tb_ring { #define RING_FLAG_NO_SUSPEND BIT(0) /* Configure the ring to be in frame mode */ #define RING_FLAG_FRAME BIT(1) +/* Enable end-to-end flow control */ +#define RING_FLAG_E2E BIT(2) struct ring_frame; typedef void (*ring_cb)(struct tb_ring *, struct ring_frame *, bool canceled); @@ -562,7 +567,8 @@ struct ring_frame { struct tb_ring *tb_ring_alloc_tx(struct tb_nhi *nhi, int hop, int size, unsigned int flags); struct tb_ring *tb_ring_alloc_rx(struct tb_nhi *nhi, int hop, int size, - unsigned int flags, u16 sof_mask, u16 eof_mask, + unsigned int flags, int e2e_tx_hop, + u16 sof_mask, u16 eof_mask, void (*start_poll)(void *), void *poll_data); void tb_ring_start(struct tb_ring *ring); void tb_ring_stop(struct tb_ring *ring); From edc0f494ed966e39e5619be7cdaeb9873e1f4fe1 Mon Sep 17 00:00:00 2001 From: Isaac Hazan Date: Thu, 24 Sep 2020 11:44:02 +0300 Subject: [PATCH 050/172] thunderbolt: Add DMA traffic test driver This driver allows sending DMA traffic over XDomain connection. Specifically over a loopback connection using either a Thunderbolt/USB4 cable that is connected back to the host router port, or a special loopback dongle that has RX and TX lines crossed. This can be useful at manufacturing floor to check whether Thunderbolt/USB4 ports are functional. The driver exposes debugfs directory under the XDomain service that can be used to configure the driver, start the test and check the results. If a loopback dongle is used the steps to send and receive 1000 packets can be done like: # modprobe thunderbolt_dma_test # echo 1000 > /sys/kernel/debug/thunderbolt//dma_test/packets_to_receive # echo 1000 > /sys/kernel/debug/thunderbolt//dma_test/packets_to_send # echo 1 > /sys/kernel/debug/thunderbolt//dma_test/test # cat /sys/kernel/debug/thunderbolt//dma_test/status When a cable is connected back to host then there are two Thunderbolt services, one is configured for receiving (does not matter which one): # modprobe thunderbolt_dma_test # echo 1000 > /sys/kernel/debug/thunderbolt//dma_test/packets_to_receive # echo 1 > /sys/kernel/debug/thunderbolt//dma_test/test The other one for sending: # echo 1000 > /sys/kernel/debug/thunderbolt//dma_test/packets_to_send # echo 1 > /sys/kernel/debug/thunderbolt//dma_test/test Results can be read from both services status attributes. Signed-off-by: Isaac Hazan Signed-off-by: Mika Westerberg Acked-by: Yehezkel Bernat Reviewed-by: Greg Kroah-Hartman --- drivers/thunderbolt/Kconfig | 13 + drivers/thunderbolt/Makefile | 3 + drivers/thunderbolt/dma_test.c | 736 +++++++++++++++++++++++++++++++++ 3 files changed, 752 insertions(+) create mode 100644 drivers/thunderbolt/dma_test.c diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig index 7fc058f81d00..4bfec8a28064 100644 --- a/drivers/thunderbolt/Kconfig +++ b/drivers/thunderbolt/Kconfig @@ -31,4 +31,17 @@ config USB4_KUNIT_TEST bool "KUnit tests" depends on KUNIT=y +config USB4_DMA_TEST + tristate "DMA traffic test driver" + depends on DEBUG_FS + help + This allows sending and receiving DMA traffic through loopback + connection. Loopback connection can be done by either special + dongle that has TX/RX lines crossed, or by simply connecting a + cable back to the host. Only enable this if you know what you + are doing. Normal users and distro kernels should say N here. + + To compile this driver a module, choose M here. The module will be + called thunderbolt_dma_test. + endif # USB4 diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 571537371072..7aa48f6c41d9 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -7,3 +7,6 @@ thunderbolt-objs += nvm.o retimer.o quirks.o thunderbolt-${CONFIG_ACPI} += acpi.o thunderbolt-$(CONFIG_DEBUG_FS) += debugfs.o thunderbolt-${CONFIG_USB4_KUNIT_TEST} += test.o + +thunderbolt_dma_test-${CONFIG_USB4_DMA_TEST} += dma_test.o +obj-$(CONFIG_USB4_DMA_TEST) += thunderbolt_dma_test.o diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c new file mode 100644 index 000000000000..f924423fa180 --- /dev/null +++ b/drivers/thunderbolt/dma_test.c @@ -0,0 +1,736 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DMA traffic test driver + * + * Copyright (C) 2020, Intel Corporation + * Authors: Isaac Hazan + * Mika Westerberg + */ + +#include +#include +#include +#include +#include +#include + +#define DMA_TEST_HOPID 8 +#define DMA_TEST_TX_RING_SIZE 64 +#define DMA_TEST_RX_RING_SIZE 256 +#define DMA_TEST_FRAME_SIZE SZ_4K +#define DMA_TEST_DATA_PATTERN 0x0123456789abcdefLL +#define DMA_TEST_MAX_PACKETS 1000 + +enum dma_test_frame_pdf { + DMA_TEST_PDF_FRAME_START = 1, + DMA_TEST_PDF_FRAME_END, +}; + +struct dma_test_frame { + struct dma_test *dma_test; + void *data; + struct ring_frame frame; +}; + +enum dma_test_test_error { + DMA_TEST_NO_ERROR, + DMA_TEST_INTERRUPTED, + DMA_TEST_BUFFER_ERROR, + DMA_TEST_DMA_ERROR, + DMA_TEST_CONFIG_ERROR, + DMA_TEST_SPEED_ERROR, + DMA_TEST_WIDTH_ERROR, + DMA_TEST_BONDING_ERROR, + DMA_TEST_PACKET_ERROR, +}; + +static const char * const dma_test_error_names[] = { + [DMA_TEST_NO_ERROR] = "no errors", + [DMA_TEST_INTERRUPTED] = "interrupted by signal", + [DMA_TEST_BUFFER_ERROR] = "no memory for packet buffers", + [DMA_TEST_DMA_ERROR] = "DMA ring setup failed", + [DMA_TEST_CONFIG_ERROR] = "configuration is not valid", + [DMA_TEST_SPEED_ERROR] = "unexpected link speed", + [DMA_TEST_WIDTH_ERROR] = "unexpected link width", + [DMA_TEST_BONDING_ERROR] = "lane bonding configuration error", + [DMA_TEST_PACKET_ERROR] = "packet check failed", +}; + +enum dma_test_result { + DMA_TEST_NOT_RUN, + DMA_TEST_SUCCESS, + DMA_TEST_FAIL, +}; + +static const char * const dma_test_result_names[] = { + [DMA_TEST_NOT_RUN] = "not run", + [DMA_TEST_SUCCESS] = "success", + [DMA_TEST_FAIL] = "failed", +}; + +/** + * struct dma_test - DMA test device driver private data + * @svc: XDomain service the driver is bound to + * @xd: XDomain the service belongs to + * @rx_ring: Software ring holding RX frames + * @tx_ring: Software ring holding TX frames + * @packets_to_send: Number of packets to send + * @packets_to_receive: Number of packets to receive + * @packets_sent: Actual number of packets sent + * @packets_received: Actual number of packets received + * @link_speed: Expected link speed (Gb/s), %0 to use whatever is negotiated + * @link_width: Expected link width (Gb/s), %0 to use whatever is negotiated + * @crc_errors: Number of CRC errors during the test run + * @buffer_overflow_errors: Number of buffer overflow errors during the test + * run + * @result: Result of the last run + * @error_code: Error code of the last run + * @complete: Used to wait for the Rx to complete + * @lock: Lock serializing access to this structure + * @debugfs_dir: dentry of this dma_test + */ +struct dma_test { + const struct tb_service *svc; + struct tb_xdomain *xd; + struct tb_ring *rx_ring; + struct tb_ring *tx_ring; + unsigned int packets_to_send; + unsigned int packets_to_receive; + unsigned int packets_sent; + unsigned int packets_received; + unsigned int link_speed; + unsigned int link_width; + unsigned int crc_errors; + unsigned int buffer_overflow_errors; + enum dma_test_result result; + enum dma_test_test_error error_code; + struct completion complete; + struct mutex lock; + struct dentry *debugfs_dir; +}; + +/* DMA test property directory UUID: 3188cd10-6523-4a5a-a682-fdca07a248d8 */ +static const uuid_t dma_test_dir_uuid = + UUID_INIT(0x3188cd10, 0x6523, 0x4a5a, + 0xa6, 0x82, 0xfd, 0xca, 0x07, 0xa2, 0x48, 0xd8); + +static struct tb_property_dir *dma_test_dir; +static void *dma_test_pattern; + +static void dma_test_free_rings(struct dma_test *dt) +{ + if (dt->rx_ring) { + tb_ring_free(dt->rx_ring); + dt->rx_ring = NULL; + } + if (dt->tx_ring) { + tb_ring_free(dt->tx_ring); + dt->tx_ring = NULL; + } +} + +static int dma_test_start_rings(struct dma_test *dt) +{ + unsigned int flags = RING_FLAG_FRAME; + struct tb_xdomain *xd = dt->xd; + int ret, e2e_tx_hop = 0; + struct tb_ring *ring; + + /* + * If we are both sender and receiver (traffic goes over a + * special loopback dongle) enable E2E flow control. This avoids + * losing packets. + */ + if (dt->packets_to_send && dt->packets_to_receive) + flags |= RING_FLAG_E2E; + + if (dt->packets_to_send) { + ring = tb_ring_alloc_tx(xd->tb->nhi, -1, DMA_TEST_TX_RING_SIZE, + flags); + if (!ring) + return -ENOMEM; + + dt->tx_ring = ring; + e2e_tx_hop = ring->hop; + } + + if (dt->packets_to_receive) { + u16 sof_mask, eof_mask; + + sof_mask = BIT(DMA_TEST_PDF_FRAME_START); + eof_mask = BIT(DMA_TEST_PDF_FRAME_END); + + ring = tb_ring_alloc_rx(xd->tb->nhi, -1, DMA_TEST_RX_RING_SIZE, + flags, e2e_tx_hop, sof_mask, eof_mask, + NULL, NULL); + if (!ring) { + dma_test_free_rings(dt); + return -ENOMEM; + } + + dt->rx_ring = ring; + } + + ret = tb_xdomain_enable_paths(dt->xd, DMA_TEST_HOPID, + dt->tx_ring ? dt->tx_ring->hop : 0, + DMA_TEST_HOPID, + dt->rx_ring ? dt->rx_ring->hop : 0); + if (ret) { + dma_test_free_rings(dt); + return ret; + } + + if (dt->tx_ring) + tb_ring_start(dt->tx_ring); + if (dt->rx_ring) + tb_ring_start(dt->rx_ring); + + return 0; +} + +static void dma_test_stop_rings(struct dma_test *dt) +{ + if (dt->rx_ring) + tb_ring_stop(dt->rx_ring); + if (dt->tx_ring) + tb_ring_stop(dt->tx_ring); + + if (tb_xdomain_disable_paths(dt->xd)) + dev_warn(&dt->svc->dev, "failed to disable DMA paths\n"); + + dma_test_free_rings(dt); +} + +static void dma_test_rx_callback(struct tb_ring *ring, struct ring_frame *frame, + bool canceled) +{ + struct dma_test_frame *tf = container_of(frame, typeof(*tf), frame); + struct dma_test *dt = tf->dma_test; + struct device *dma_dev = tb_ring_dma_device(dt->rx_ring); + + dma_unmap_single(dma_dev, tf->frame.buffer_phy, DMA_TEST_FRAME_SIZE, + DMA_FROM_DEVICE); + kfree(tf->data); + + if (canceled) { + kfree(tf); + return; + } + + dt->packets_received++; + dev_dbg(&dt->svc->dev, "packet %u/%u received\n", dt->packets_received, + dt->packets_to_receive); + + if (tf->frame.flags & RING_DESC_CRC_ERROR) + dt->crc_errors++; + if (tf->frame.flags & RING_DESC_BUFFER_OVERRUN) + dt->buffer_overflow_errors++; + + kfree(tf); + + if (dt->packets_received == dt->packets_to_receive) + complete(&dt->complete); +} + +static int dma_test_submit_rx(struct dma_test *dt, size_t npackets) +{ + struct device *dma_dev = tb_ring_dma_device(dt->rx_ring); + int i; + + for (i = 0; i < npackets; i++) { + struct dma_test_frame *tf; + dma_addr_t dma_addr; + + tf = kzalloc(sizeof(*tf), GFP_KERNEL); + if (!tf) + return -ENOMEM; + + tf->data = kzalloc(DMA_TEST_FRAME_SIZE, GFP_KERNEL); + if (!tf->data) { + kfree(tf); + return -ENOMEM; + } + + dma_addr = dma_map_single(dma_dev, tf->data, DMA_TEST_FRAME_SIZE, + DMA_FROM_DEVICE); + if (dma_mapping_error(dma_dev, dma_addr)) { + kfree(tf->data); + kfree(tf); + return -ENOMEM; + } + + tf->frame.buffer_phy = dma_addr; + tf->frame.callback = dma_test_rx_callback; + tf->dma_test = dt; + INIT_LIST_HEAD(&tf->frame.list); + + tb_ring_rx(dt->rx_ring, &tf->frame); + } + + return 0; +} + +static void dma_test_tx_callback(struct tb_ring *ring, struct ring_frame *frame, + bool canceled) +{ + struct dma_test_frame *tf = container_of(frame, typeof(*tf), frame); + struct dma_test *dt = tf->dma_test; + struct device *dma_dev = tb_ring_dma_device(dt->tx_ring); + + dma_unmap_single(dma_dev, tf->frame.buffer_phy, DMA_TEST_FRAME_SIZE, + DMA_TO_DEVICE); + kfree(tf->data); + kfree(tf); +} + +static int dma_test_submit_tx(struct dma_test *dt, size_t npackets) +{ + struct device *dma_dev = tb_ring_dma_device(dt->tx_ring); + int i; + + for (i = 0; i < npackets; i++) { + struct dma_test_frame *tf; + dma_addr_t dma_addr; + + tf = kzalloc(sizeof(*tf), GFP_KERNEL); + if (!tf) + return -ENOMEM; + + tf->frame.size = 0; /* means 4096 */ + tf->dma_test = dt; + + tf->data = kzalloc(DMA_TEST_FRAME_SIZE, GFP_KERNEL); + if (!tf->data) { + kfree(tf); + return -ENOMEM; + } + + memcpy(tf->data, dma_test_pattern, DMA_TEST_FRAME_SIZE); + + dma_addr = dma_map_single(dma_dev, tf->data, DMA_TEST_FRAME_SIZE, + DMA_TO_DEVICE); + if (dma_mapping_error(dma_dev, dma_addr)) { + kfree(tf->data); + kfree(tf); + return -ENOMEM; + } + + tf->frame.buffer_phy = dma_addr; + tf->frame.callback = dma_test_tx_callback; + tf->frame.sof = DMA_TEST_PDF_FRAME_START; + tf->frame.eof = DMA_TEST_PDF_FRAME_END; + INIT_LIST_HEAD(&tf->frame.list); + + dt->packets_sent++; + dev_dbg(&dt->svc->dev, "packet %u/%u sent\n", dt->packets_sent, + dt->packets_to_send); + + tb_ring_tx(dt->tx_ring, &tf->frame); + } + + return 0; +} + +#define DMA_TEST_DEBUGFS_ATTR(__fops, __get, __validate, __set) \ +static int __fops ## _show(void *data, u64 *val) \ +{ \ + struct tb_service *svc = data; \ + struct dma_test *dt = tb_service_get_drvdata(svc); \ + int ret; \ + \ + ret = mutex_lock_interruptible(&dt->lock); \ + if (ret) \ + return ret; \ + __get(dt, val); \ + mutex_unlock(&dt->lock); \ + return 0; \ +} \ +static int __fops ## _store(void *data, u64 val) \ +{ \ + struct tb_service *svc = data; \ + struct dma_test *dt = tb_service_get_drvdata(svc); \ + int ret; \ + \ + ret = __validate(val); \ + if (ret) \ + return ret; \ + ret = mutex_lock_interruptible(&dt->lock); \ + if (ret) \ + return ret; \ + __set(dt, val); \ + mutex_unlock(&dt->lock); \ + return 0; \ +} \ +DEFINE_DEBUGFS_ATTRIBUTE(__fops ## _fops, __fops ## _show, \ + __fops ## _store, "%llu\n") + +static void lanes_get(const struct dma_test *dt, u64 *val) +{ + *val = dt->link_width; +} + +static int lanes_validate(u64 val) +{ + return val > 2 ? -EINVAL : 0; +} + +static void lanes_set(struct dma_test *dt, u64 val) +{ + dt->link_width = val; +} +DMA_TEST_DEBUGFS_ATTR(lanes, lanes_get, lanes_validate, lanes_set); + +static void speed_get(const struct dma_test *dt, u64 *val) +{ + *val = dt->link_speed; +} + +static int speed_validate(u64 val) +{ + switch (val) { + case 20: + case 10: + case 0: + return 0; + default: + return -EINVAL; + } +} + +static void speed_set(struct dma_test *dt, u64 val) +{ + dt->link_speed = val; +} +DMA_TEST_DEBUGFS_ATTR(speed, speed_get, speed_validate, speed_set); + +static void packets_to_receive_get(const struct dma_test *dt, u64 *val) +{ + *val = dt->packets_to_receive; +} + +static int packets_to_receive_validate(u64 val) +{ + return val > DMA_TEST_MAX_PACKETS ? -EINVAL : 0; +} + +static void packets_to_receive_set(struct dma_test *dt, u64 val) +{ + dt->packets_to_receive = val; +} +DMA_TEST_DEBUGFS_ATTR(packets_to_receive, packets_to_receive_get, + packets_to_receive_validate, packets_to_receive_set); + +static void packets_to_send_get(const struct dma_test *dt, u64 *val) +{ + *val = dt->packets_to_send; +} + +static int packets_to_send_validate(u64 val) +{ + return val > DMA_TEST_MAX_PACKETS ? -EINVAL : 0; +} + +static void packets_to_send_set(struct dma_test *dt, u64 val) +{ + dt->packets_to_send = val; +} +DMA_TEST_DEBUGFS_ATTR(packets_to_send, packets_to_send_get, + packets_to_send_validate, packets_to_send_set); + +static int dma_test_set_bonding(struct dma_test *dt) +{ + switch (dt->link_width) { + case 2: + return tb_xdomain_lane_bonding_enable(dt->xd); + case 1: + tb_xdomain_lane_bonding_disable(dt->xd); + fallthrough; + default: + return 0; + } +} + +static bool dma_test_validate_config(struct dma_test *dt) +{ + if (!dt->packets_to_send && !dt->packets_to_receive) + return false; + if (dt->packets_to_send && dt->packets_to_receive && + dt->packets_to_send != dt->packets_to_receive) + return false; + return true; +} + +static void dma_test_check_errors(struct dma_test *dt, int ret) +{ + if (!dt->error_code) { + if (dt->link_speed && dt->xd->link_speed != dt->link_speed) { + dt->error_code = DMA_TEST_SPEED_ERROR; + } else if (dt->link_width && + dt->xd->link_width != dt->link_width) { + dt->error_code = DMA_TEST_WIDTH_ERROR; + } else if (dt->packets_to_send != dt->packets_sent || + dt->packets_to_receive != dt->packets_received || + dt->crc_errors || dt->buffer_overflow_errors) { + dt->error_code = DMA_TEST_PACKET_ERROR; + } else { + return; + } + } + + dt->result = DMA_TEST_FAIL; +} + +static int test_store(void *data, u64 val) +{ + struct tb_service *svc = data; + struct dma_test *dt = tb_service_get_drvdata(svc); + int ret; + + if (val != 1) + return -EINVAL; + + ret = mutex_lock_interruptible(&dt->lock); + if (ret) + return ret; + + dt->packets_sent = 0; + dt->packets_received = 0; + dt->crc_errors = 0; + dt->buffer_overflow_errors = 0; + dt->result = DMA_TEST_SUCCESS; + dt->error_code = DMA_TEST_NO_ERROR; + + dev_dbg(&svc->dev, "DMA test starting\n"); + if (dt->link_speed) + dev_dbg(&svc->dev, "link_speed: %u Gb/s\n", dt->link_speed); + if (dt->link_width) + dev_dbg(&svc->dev, "link_width: %u\n", dt->link_width); + dev_dbg(&svc->dev, "packets_to_send: %u\n", dt->packets_to_send); + dev_dbg(&svc->dev, "packets_to_receive: %u\n", dt->packets_to_receive); + + if (!dma_test_validate_config(dt)) { + dev_err(&svc->dev, "invalid test configuration\n"); + dt->error_code = DMA_TEST_CONFIG_ERROR; + goto out_unlock; + } + + ret = dma_test_set_bonding(dt); + if (ret) { + dev_err(&svc->dev, "failed to set lanes\n"); + dt->error_code = DMA_TEST_BONDING_ERROR; + goto out_unlock; + } + + ret = dma_test_start_rings(dt); + if (ret) { + dev_err(&svc->dev, "failed to enable DMA rings\n"); + dt->error_code = DMA_TEST_DMA_ERROR; + goto out_unlock; + } + + if (dt->packets_to_receive) { + reinit_completion(&dt->complete); + ret = dma_test_submit_rx(dt, dt->packets_to_receive); + if (ret) { + dev_err(&svc->dev, "failed to submit receive buffers\n"); + dt->error_code = DMA_TEST_BUFFER_ERROR; + goto out_stop; + } + } + + if (dt->packets_to_send) { + ret = dma_test_submit_tx(dt, dt->packets_to_send); + if (ret) { + dev_err(&svc->dev, "failed to submit transmit buffers\n"); + dt->error_code = DMA_TEST_BUFFER_ERROR; + goto out_stop; + } + } + + if (dt->packets_to_receive) { + ret = wait_for_completion_interruptible(&dt->complete); + if (ret) { + dt->error_code = DMA_TEST_INTERRUPTED; + goto out_stop; + } + } + +out_stop: + dma_test_stop_rings(dt); +out_unlock: + dma_test_check_errors(dt, ret); + mutex_unlock(&dt->lock); + + dev_dbg(&svc->dev, "DMA test %s\n", dma_test_result_names[dt->result]); + return ret; +} +DEFINE_DEBUGFS_ATTRIBUTE(test_fops, NULL, test_store, "%llu\n"); + +static int status_show(struct seq_file *s, void *not_used) +{ + struct tb_service *svc = s->private; + struct dma_test *dt = tb_service_get_drvdata(svc); + int ret; + + ret = mutex_lock_interruptible(&dt->lock); + if (ret) + return ret; + + seq_printf(s, "result: %s\n", dma_test_result_names[dt->result]); + if (dt->result == DMA_TEST_NOT_RUN) + goto out_unlock; + + seq_printf(s, "packets received: %u\n", dt->packets_received); + seq_printf(s, "packets sent: %u\n", dt->packets_sent); + seq_printf(s, "CRC errors: %u\n", dt->crc_errors); + seq_printf(s, "buffer overflow errors: %u\n", + dt->buffer_overflow_errors); + seq_printf(s, "error: %s\n", dma_test_error_names[dt->error_code]); + +out_unlock: + mutex_unlock(&dt->lock); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(status); + +static void dma_test_debugfs_init(struct tb_service *svc) +{ + struct dma_test *dt = tb_service_get_drvdata(svc); + + dt->debugfs_dir = debugfs_create_dir("dma_test", svc->debugfs_dir); + + debugfs_create_file("lanes", 0600, dt->debugfs_dir, svc, &lanes_fops); + debugfs_create_file("speed", 0600, dt->debugfs_dir, svc, &speed_fops); + debugfs_create_file("packets_to_receive", 0600, dt->debugfs_dir, svc, + &packets_to_receive_fops); + debugfs_create_file("packets_to_send", 0600, dt->debugfs_dir, svc, + &packets_to_send_fops); + debugfs_create_file("status", 0400, dt->debugfs_dir, svc, &status_fops); + debugfs_create_file("test", 0200, dt->debugfs_dir, svc, &test_fops); +} + +static int dma_test_probe(struct tb_service *svc, const struct tb_service_id *id) +{ + struct tb_xdomain *xd = tb_service_parent(svc); + struct dma_test *dt; + + dt = devm_kzalloc(&svc->dev, sizeof(*dt), GFP_KERNEL); + if (!dt) + return -ENOMEM; + + dt->svc = svc; + dt->xd = xd; + mutex_init(&dt->lock); + init_completion(&dt->complete); + + tb_service_set_drvdata(svc, dt); + dma_test_debugfs_init(svc); + + return 0; +} + +static void dma_test_remove(struct tb_service *svc) +{ + struct dma_test *dt = tb_service_get_drvdata(svc); + + mutex_lock(&dt->lock); + debugfs_remove_recursive(dt->debugfs_dir); + mutex_unlock(&dt->lock); +} + +static int __maybe_unused dma_test_suspend(struct device *dev) +{ + /* + * No need to do anything special here. If userspace is writing + * to the test attribute when suspend started, it comes out from + * wait_for_completion_interruptible() with -ERESTARTSYS and the + * DMA test fails tearing down the rings. Once userspace is + * thawed the kernel restarts the write syscall effectively + * re-running the test. + */ + return 0; +} + +static int __maybe_unused dma_test_resume(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops dma_test_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dma_test_suspend, dma_test_resume) +}; + +static const struct tb_service_id dma_test_ids[] = { + { TB_SERVICE("dma_test", 1) }, + { }, +}; +MODULE_DEVICE_TABLE(tbsvc, dma_test_ids); + +static struct tb_service_driver dma_test_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "thunderbolt_dma_test", + .pm = &dma_test_pm_ops, + }, + .probe = dma_test_probe, + .remove = dma_test_remove, + .id_table = dma_test_ids, +}; + +static int __init dma_test_init(void) +{ + u64 data_value = DMA_TEST_DATA_PATTERN; + int i, ret; + + dma_test_pattern = kmalloc(DMA_TEST_FRAME_SIZE, GFP_KERNEL); + if (!dma_test_pattern) + return -ENOMEM; + + for (i = 0; i < DMA_TEST_FRAME_SIZE / sizeof(data_value); i++) + ((u32 *)dma_test_pattern)[i] = data_value++; + + dma_test_dir = tb_property_create_dir(&dma_test_dir_uuid); + if (!dma_test_dir) { + ret = -ENOMEM; + goto err_free_pattern; + } + + tb_property_add_immediate(dma_test_dir, "prtcid", 1); + tb_property_add_immediate(dma_test_dir, "prtcvers", 1); + tb_property_add_immediate(dma_test_dir, "prtcrevs", 0); + tb_property_add_immediate(dma_test_dir, "prtcstns", 0); + + ret = tb_register_property_dir("dma_test", dma_test_dir); + if (ret) + goto err_free_dir; + + ret = tb_register_service_driver(&dma_test_driver); + if (ret) + goto err_unregister_dir; + + return 0; + +err_unregister_dir: + tb_unregister_property_dir("dma_test", dma_test_dir); +err_free_dir: + tb_property_free_dir(dma_test_dir); +err_free_pattern: + kfree(dma_test_pattern); + + return ret; +} +module_init(dma_test_init); + +static void __exit dma_test_exit(void) +{ + tb_unregister_service_driver(&dma_test_driver); + tb_unregister_property_dir("dma_test", dma_test_dir); + tb_property_free_dir(dma_test_dir); + kfree(dma_test_pattern); +} +module_exit(dma_test_exit); + +MODULE_AUTHOR("Isaac Hazan "); +MODULE_AUTHOR("Mika Westerberg "); +MODULE_DESCRIPTION("DMA traffic test driver"); +MODULE_LICENSE("GPL v2"); From 4e58171aa93fe8caf4f6e5e9972b7abe117c0014 Mon Sep 17 00:00:00 2001 From: Isaac Hazan Date: Tue, 3 Nov 2020 15:57:32 +0300 Subject: [PATCH 051/172] MAINTAINERS: Add Isaac as maintainer of Thunderbolt DMA traffic test driver I will be maintaining the Thunderbolt DMA traffic test driver. Signed-off-by: Isaac Hazan Signed-off-by: Mika Westerberg Acked-by: Yehezkel Bernat Reviewed-by: Greg Kroah-Hartman --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3da6d8c154e4..83c4c66f8188 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17378,6 +17378,12 @@ W: http://thinkwiki.org/wiki/Ibm-acpi T: git git://repo.or.cz/linux-2.6/linux-acpi-2.6/ibm-acpi-2.6.git F: drivers/platform/x86/thinkpad_acpi.c +THUNDERBOLT DMA TRAFFIC TEST DRIVER +M: Isaac Hazan +L: linux-usb@vger.kernel.org +S: Maintained +F: drivers/thunderbolt/dma_test.c + THUNDERBOLT DRIVER M: Andreas Noever M: Michael Jamet From 1d6903a617a221f9d8295847ffaa3c39cd6b13ba Mon Sep 17 00:00:00 2001 From: Nick Desaulniers Date: Tue, 10 Nov 2020 17:47:14 -0800 Subject: [PATCH 052/172] usb: fix a few cases of -Wfallthrough The "fallthrough" pseudo-keyword was added as a portable way to denote intentional fallthrough. Clang will still warn on cases where there is a fallthrough to an immediate break. Add explicit breaks for those cases. Reviewed-by: Nathan Chancellor Signed-off-by: Nick Desaulniers Link: https://lore.kernel.org/r/20201111014716.260633-1-ndesaulniers@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/config.c | 1 + drivers/usb/host/ehci-hcd.c | 2 +- drivers/usb/host/ohci-hcd.c | 2 +- drivers/usb/host/ohci-hub.c | 1 + drivers/usb/host/xhci-ring.c | 2 ++ 5 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 562a730befda..b199eb65f378 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -1076,6 +1076,7 @@ int usb_get_bos_descriptor(struct usb_device *dev) case USB_PTM_CAP_TYPE: dev->bos->ptm_cap = (struct usb_ptm_cap_descriptor *)buffer; + break; default: break; } diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 3575b7201881..e358ae17d51e 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -867,7 +867,7 @@ static int ehci_urb_enqueue ( */ if (urb->transfer_buffer_length > (16 * 1024)) return -EMSGSIZE; - /* FALLTHROUGH */ + fallthrough; /* case PIPE_BULK: */ default: if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)) diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 73e13e7c2b46..1f5e69314a17 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -171,7 +171,7 @@ static int ohci_urb_enqueue ( /* 1 TD for setup, 1 for ACK, plus ... */ size = 2; - /* FALLTHROUGH */ + fallthrough; // case PIPE_INTERRUPT: // case PIPE_BULK: default: diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c index 44504c1751e0..f474f2f9c1e4 100644 --- a/drivers/usb/host/ohci-hub.c +++ b/drivers/usb/host/ohci-hub.c @@ -692,6 +692,7 @@ int ohci_hub_control( case C_HUB_OVER_CURRENT: ohci_writel (ohci, RH_HS_OCIC, &ohci->regs->roothub.status); + break; case C_HUB_LOCAL_POWER: break; default: diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 167dae117f73..eac43a7b7f23 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2418,6 +2418,7 @@ static int handle_tx_event(struct xhci_hcd *xhci, xhci_warn_ratelimited(xhci, "WARN Successful completion on short TX for slot %u ep %u: needs XHCI_TRUST_TX_LENGTH quirk?\n", slot_id, ep_index); + break; case COMP_SHORT_PACKET: break; /* Completion codes for endpoint stopped state */ @@ -2962,6 +2963,7 @@ static int prepare_ring(struct xhci_hcd *xhci, struct xhci_ring *ep_ring, return -EINVAL; case EP_STATE_HALTED: xhci_dbg(xhci, "WARN halted endpoint, queueing URB anyway.\n"); + break; case EP_STATE_STOPPED: case EP_STATE_RUNNING: break; From 6a6516c024bb90058f835364b28ee8e1fee8037d Mon Sep 17 00:00:00 2001 From: Lukas Bulwahn Date: Thu, 12 Nov 2020 20:12:55 +0100 Subject: [PATCH 053/172] USB: storage: avoid use of uninitialized values in error path When usb_stor_bulk_transfer_sglist() returns with USB_STOR_XFER_ERROR, it returns without writing to its parameter *act_len. Further, the two callers of usb_stor_bulk_transfer_sglist(): usb_stor_bulk_srb() and usb_stor_bulk_transfer_sg(), use the passed variable partial without checking the return value. Hence, the uninitialized value of partial is then used in the further execution of those two functions. Clang-analyzer detects this potential control and data flow and warns: drivers/usb/storage/transport.c:469:40: warning: The right operand of '-' is a garbage value [clang-analyzer-core.UndefinedBinaryOperatorResult] scsi_set_resid(srb, scsi_bufflen(srb) - partial); ^ drivers/usb/storage/transport.c:495:15: warning: Assigned value is garbage or undefined [clang-analyzer-core.uninitialized.Assign] length_left -= partial; ^ When a transfer error occurs, the *act_len value is probably ignored by the higher layers. But it won't hurt to set it to a valid number, just in case. For the two early-return paths in usb_stor_bulk_transfer_sglist(), the amount of data transferred is 0. So if act_len is not NULL, set *act_len to 0 in those paths. That makes clang-analyzer happy. Proposal was discussed in this mail thread: https://lore.kernel.org/linux-usb/alpine.DEB.2.21.2011112146110.13119@felia/ Reviewed-by: Nathan Chancellor Acked-by: Alan Stern Signed-off-by: Lukas Bulwahn Link: https://lore.kernel.org/r/20201112191255.13372-1-lukas.bulwahn@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/transport.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/usb/storage/transport.c b/drivers/usb/storage/transport.c index 238a8088e17f..5eb895b19c55 100644 --- a/drivers/usb/storage/transport.c +++ b/drivers/usb/storage/transport.c @@ -416,7 +416,7 @@ static int usb_stor_bulk_transfer_sglist(struct us_data *us, unsigned int pipe, /* don't submit s-g requests during abort processing */ if (test_bit(US_FLIDX_ABORTING, &us->dflags)) - return USB_STOR_XFER_ERROR; + goto usb_stor_xfer_error; /* initialize the scatter-gather request block */ usb_stor_dbg(us, "xfer %u bytes, %d entries\n", length, num_sg); @@ -424,7 +424,7 @@ static int usb_stor_bulk_transfer_sglist(struct us_data *us, unsigned int pipe, sg, num_sg, length, GFP_NOIO); if (result) { usb_stor_dbg(us, "usb_sg_init returned %d\n", result); - return USB_STOR_XFER_ERROR; + goto usb_stor_xfer_error; } /* @@ -452,6 +452,11 @@ static int usb_stor_bulk_transfer_sglist(struct us_data *us, unsigned int pipe, *act_len = us->current_sg.bytes; return interpret_urb_result(us, pipe, length, result, us->current_sg.bytes); + +usb_stor_xfer_error: + if (act_len) + *act_len = 0; + return USB_STOR_XFER_ERROR; } /* From e625f3dede4a2df6df914b4a0fb3e49379676dfc Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Mon, 9 Nov 2020 18:08:13 -0300 Subject: [PATCH 054/172] usb: host: imx21-hcd: Remove the driver Since commit 4b563a066611 ("ARM: imx: Remove imx21 support") the imx21 SoC is no longer supported. Get rid of its USB driver too, which is now unused. Acked-by: Peter Chen Signed-off-by: Fabio Estevam Link: https://lore.kernel.org/r/20201109210813.21382-1-festevam@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/Makefile | 1 - drivers/usb/host/Kconfig | 10 - drivers/usb/host/Makefile | 1 - drivers/usb/host/imx21-dbg.c | 439 -------- drivers/usb/host/imx21-hcd.c | 1933 ---------------------------------- drivers/usb/host/imx21-hcd.h | 431 -------- 6 files changed, 2815 deletions(-) delete mode 100644 drivers/usb/host/imx21-dbg.c delete mode 100644 drivers/usb/host/imx21-hcd.c delete mode 100644 drivers/usb/host/imx21-hcd.h diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 1c1c1d659394..ba5706ccc188 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -30,7 +30,6 @@ obj-$(CONFIG_USB_ISP1362_HCD) += host/ obj-$(CONFIG_USB_U132_HCD) += host/ obj-$(CONFIG_USB_R8A66597_HCD) += host/ obj-$(CONFIG_USB_HWA_HCD) += host/ -obj-$(CONFIG_USB_IMX21_HCD) += host/ obj-$(CONFIG_USB_FSL_USB2) += host/ obj-$(CONFIG_USB_FOTG210_HCD) += host/ obj-$(CONFIG_USB_MAX3421_HCD) += host/ diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index ab12c4bf0ef1..a633e9c15f7e 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -741,16 +741,6 @@ config USB_RENESAS_USBHS_HCD To compile this driver as a module, choose M here: the module will be called renesas-usbhs. -config USB_IMX21_HCD - tristate "i.MX21 HCD support" - depends on ARM && ARCH_MXC - help - This driver enables support for the on-chip USB host in the - i.MX21 processor. - - To compile this driver as a module, choose M here: the - module will be called "imx21-hcd". - config USB_HCD_BCMA tristate "BCMA usb host driver" depends on BCMA diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index bc731332fed9..d03f6f0764d2 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -81,7 +81,6 @@ obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o obj-$(CONFIG_USB_R8A66597_HCD) += r8a66597-hcd.o -obj-$(CONFIG_USB_IMX21_HCD) += imx21-hcd.o obj-$(CONFIG_USB_FSL_USB2) += fsl-mph-dr-of.o obj-$(CONFIG_USB_EHCI_FSL) += fsl-mph-dr-of.o obj-$(CONFIG_USB_EHCI_FSL) += ehci-fsl.o diff --git a/drivers/usb/host/imx21-dbg.c b/drivers/usb/host/imx21-dbg.c deleted file mode 100644 index 02a1344fbd6a..000000000000 --- a/drivers/usb/host/imx21-dbg.c +++ /dev/null @@ -1,439 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (c) 2009 by Martin Fuzzey - */ - -/* this file is part of imx21-hcd.c */ - -#ifdef CONFIG_DYNAMIC_DEBUG -#define DEBUG -#endif - -#ifndef DEBUG - -static inline void create_debug_files(struct imx21 *imx21) { } -static inline void remove_debug_files(struct imx21 *imx21) { } -static inline void debug_urb_submitted(struct imx21 *imx21, struct urb *urb) {} -static inline void debug_urb_completed(struct imx21 *imx21, struct urb *urb, - int status) {} -static inline void debug_urb_unlinked(struct imx21 *imx21, struct urb *urb) {} -static inline void debug_urb_queued_for_etd(struct imx21 *imx21, - struct urb *urb) {} -static inline void debug_urb_queued_for_dmem(struct imx21 *imx21, - struct urb *urb) {} -static inline void debug_etd_allocated(struct imx21 *imx21) {} -static inline void debug_etd_freed(struct imx21 *imx21) {} -static inline void debug_dmem_allocated(struct imx21 *imx21, int size) {} -static inline void debug_dmem_freed(struct imx21 *imx21, int size) {} -static inline void debug_isoc_submitted(struct imx21 *imx21, - int frame, struct td *td) {} -static inline void debug_isoc_completed(struct imx21 *imx21, - int frame, struct td *td, int cc, int len) {} - -#else - -#include -#include - -static const char *dir_labels[] = { - "TD 0", - "OUT", - "IN", - "TD 1" -}; - -static const char *speed_labels[] = { - "Full", - "Low" -}; - -static const char *format_labels[] = { - "Control", - "ISO", - "Bulk", - "Interrupt" -}; - -static inline struct debug_stats *stats_for_urb(struct imx21 *imx21, - struct urb *urb) -{ - return usb_pipeisoc(urb->pipe) ? - &imx21->isoc_stats : &imx21->nonisoc_stats; -} - -static void debug_urb_submitted(struct imx21 *imx21, struct urb *urb) -{ - stats_for_urb(imx21, urb)->submitted++; -} - -static void debug_urb_completed(struct imx21 *imx21, struct urb *urb, int st) -{ - if (st) - stats_for_urb(imx21, urb)->completed_failed++; - else - stats_for_urb(imx21, urb)->completed_ok++; -} - -static void debug_urb_unlinked(struct imx21 *imx21, struct urb *urb) -{ - stats_for_urb(imx21, urb)->unlinked++; -} - -static void debug_urb_queued_for_etd(struct imx21 *imx21, struct urb *urb) -{ - stats_for_urb(imx21, urb)->queue_etd++; -} - -static void debug_urb_queued_for_dmem(struct imx21 *imx21, struct urb *urb) -{ - stats_for_urb(imx21, urb)->queue_dmem++; -} - -static inline void debug_etd_allocated(struct imx21 *imx21) -{ - imx21->etd_usage.maximum = max( - ++(imx21->etd_usage.value), - imx21->etd_usage.maximum); -} - -static inline void debug_etd_freed(struct imx21 *imx21) -{ - imx21->etd_usage.value--; -} - -static inline void debug_dmem_allocated(struct imx21 *imx21, int size) -{ - imx21->dmem_usage.value += size; - imx21->dmem_usage.maximum = max( - imx21->dmem_usage.value, - imx21->dmem_usage.maximum); -} - -static inline void debug_dmem_freed(struct imx21 *imx21, int size) -{ - imx21->dmem_usage.value -= size; -} - - -static void debug_isoc_submitted(struct imx21 *imx21, - int frame, struct td *td) -{ - struct debug_isoc_trace *trace = &imx21->isoc_trace[ - imx21->isoc_trace_index++]; - - imx21->isoc_trace_index %= ARRAY_SIZE(imx21->isoc_trace); - trace->schedule_frame = td->frame; - trace->submit_frame = frame; - trace->request_len = td->len; - trace->td = td; -} - -static inline void debug_isoc_completed(struct imx21 *imx21, - int frame, struct td *td, int cc, int len) -{ - struct debug_isoc_trace *trace, *trace_failed; - int i; - int found = 0; - - trace = imx21->isoc_trace; - for (i = 0; i < ARRAY_SIZE(imx21->isoc_trace); i++, trace++) { - if (trace->td == td) { - trace->done_frame = frame; - trace->done_len = len; - trace->cc = cc; - trace->td = NULL; - found = 1; - break; - } - } - - if (found && cc) { - trace_failed = &imx21->isoc_trace_failed[ - imx21->isoc_trace_index_failed++]; - - imx21->isoc_trace_index_failed %= ARRAY_SIZE( - imx21->isoc_trace_failed); - *trace_failed = *trace; - } -} - - -static char *format_ep(struct usb_host_endpoint *ep, char *buf, int bufsize) -{ - if (ep) - snprintf(buf, bufsize, "ep_%02x (type:%02X kaddr:%p)", - ep->desc.bEndpointAddress, - usb_endpoint_type(&ep->desc), - ep); - else - snprintf(buf, bufsize, "none"); - return buf; -} - -static char *format_etd_dword0(u32 value, char *buf, int bufsize) -{ - snprintf(buf, bufsize, - "addr=%d ep=%d dir=%s speed=%s format=%s halted=%d", - value & 0x7F, - (value >> DW0_ENDPNT) & 0x0F, - dir_labels[(value >> DW0_DIRECT) & 0x03], - speed_labels[(value >> DW0_SPEED) & 0x01], - format_labels[(value >> DW0_FORMAT) & 0x03], - (value >> DW0_HALTED) & 0x01); - return buf; -} - -static int debug_status_show(struct seq_file *s, void *v) -{ - struct imx21 *imx21 = s->private; - int etds_allocated = 0; - int etds_sw_busy = 0; - int etds_hw_busy = 0; - int dmem_blocks = 0; - int queued_for_etd = 0; - int queued_for_dmem = 0; - unsigned int dmem_bytes = 0; - int i; - struct etd_priv *etd; - u32 etd_enable_mask; - unsigned long flags; - struct imx21_dmem_area *dmem; - struct ep_priv *ep_priv; - - spin_lock_irqsave(&imx21->lock, flags); - - etd_enable_mask = readl(imx21->regs + USBH_ETDENSET); - for (i = 0, etd = imx21->etd; i < USB_NUM_ETD; i++, etd++) { - if (etd->alloc) - etds_allocated++; - if (etd->urb) - etds_sw_busy++; - if (etd_enable_mask & (1<dmem_list, list) { - dmem_bytes += dmem->size; - dmem_blocks++; - } - - list_for_each_entry(ep_priv, &imx21->queue_for_etd, queue) - queued_for_etd++; - - list_for_each_entry(etd, &imx21->queue_for_dmem, queue) - queued_for_dmem++; - - spin_unlock_irqrestore(&imx21->lock, flags); - - seq_printf(s, - "Frame: %d\n" - "ETDs allocated: %d/%d (max=%d)\n" - "ETDs in use sw: %d\n" - "ETDs in use hw: %d\n" - "DMEM allocated: %d/%d (max=%d)\n" - "DMEM blocks: %d\n" - "Queued waiting for ETD: %d\n" - "Queued waiting for DMEM: %d\n", - readl(imx21->regs + USBH_FRMNUB) & 0xFFFF, - etds_allocated, USB_NUM_ETD, imx21->etd_usage.maximum, - etds_sw_busy, - etds_hw_busy, - dmem_bytes, DMEM_SIZE, imx21->dmem_usage.maximum, - dmem_blocks, - queued_for_etd, - queued_for_dmem); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(debug_status); - -static int debug_dmem_show(struct seq_file *s, void *v) -{ - struct imx21 *imx21 = s->private; - struct imx21_dmem_area *dmem; - unsigned long flags; - char ep_text[40]; - - spin_lock_irqsave(&imx21->lock, flags); - - list_for_each_entry(dmem, &imx21->dmem_list, list) - seq_printf(s, - "%04X: size=0x%X " - "ep=%s\n", - dmem->offset, dmem->size, - format_ep(dmem->ep, ep_text, sizeof(ep_text))); - - spin_unlock_irqrestore(&imx21->lock, flags); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(debug_dmem); - -static int debug_etd_show(struct seq_file *s, void *v) -{ - struct imx21 *imx21 = s->private; - struct etd_priv *etd; - char buf[60]; - u32 dword; - int i, j; - unsigned long flags; - - spin_lock_irqsave(&imx21->lock, flags); - - for (i = 0, etd = imx21->etd; i < USB_NUM_ETD; i++, etd++) { - int state = -1; - struct urb_priv *urb_priv; - if (etd->urb) { - urb_priv = etd->urb->hcpriv; - if (urb_priv) - state = urb_priv->state; - } - - seq_printf(s, - "etd_num: %d\n" - "ep: %s\n" - "alloc: %d\n" - "len: %d\n" - "busy sw: %d\n" - "busy hw: %d\n" - "urb state: %d\n" - "current urb: %p\n", - - i, - format_ep(etd->ep, buf, sizeof(buf)), - etd->alloc, - etd->len, - etd->urb != NULL, - (readl(imx21->regs + USBH_ETDENSET) & (1 << i)) > 0, - state, - etd->urb); - - for (j = 0; j < 4; j++) { - dword = etd_readl(imx21, i, j); - switch (j) { - case 0: - format_etd_dword0(dword, buf, sizeof(buf)); - break; - case 2: - snprintf(buf, sizeof(buf), - "cc=0X%02X", dword >> DW2_COMPCODE); - break; - default: - *buf = 0; - break; - } - seq_printf(s, - "dword %d: submitted=%08X cur=%08X [%s]\n", - j, - etd->submitted_dwords[j], - dword, - buf); - } - seq_printf(s, "\n"); - } - - spin_unlock_irqrestore(&imx21->lock, flags); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(debug_etd); - -static void debug_statistics_show_one(struct seq_file *s, - const char *name, struct debug_stats *stats) -{ - seq_printf(s, "%s:\n" - "submitted URBs: %lu\n" - "completed OK: %lu\n" - "completed failed: %lu\n" - "unlinked: %lu\n" - "queued for ETD: %lu\n" - "queued for DMEM: %lu\n\n", - name, - stats->submitted, - stats->completed_ok, - stats->completed_failed, - stats->unlinked, - stats->queue_etd, - stats->queue_dmem); -} - -static int debug_statistics_show(struct seq_file *s, void *v) -{ - struct imx21 *imx21 = s->private; - unsigned long flags; - - spin_lock_irqsave(&imx21->lock, flags); - - debug_statistics_show_one(s, "nonisoc", &imx21->nonisoc_stats); - debug_statistics_show_one(s, "isoc", &imx21->isoc_stats); - seq_printf(s, "unblock kludge triggers: %lu\n", imx21->debug_unblocks); - spin_unlock_irqrestore(&imx21->lock, flags); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(debug_statistics); - -static void debug_isoc_show_one(struct seq_file *s, - const char *name, int index, struct debug_isoc_trace *trace) -{ - seq_printf(s, "%s %d:\n" - "cc=0X%02X\n" - "scheduled frame %d (%d)\n" - "submitted frame %d (%d)\n" - "completed frame %d (%d)\n" - "requested length=%d\n" - "completed length=%d\n\n", - name, index, - trace->cc, - trace->schedule_frame, trace->schedule_frame & 0xFFFF, - trace->submit_frame, trace->submit_frame & 0xFFFF, - trace->done_frame, trace->done_frame & 0xFFFF, - trace->request_len, - trace->done_len); -} - -static int debug_isoc_show(struct seq_file *s, void *v) -{ - struct imx21 *imx21 = s->private; - struct debug_isoc_trace *trace; - unsigned long flags; - int i; - - spin_lock_irqsave(&imx21->lock, flags); - - trace = imx21->isoc_trace_failed; - for (i = 0; i < ARRAY_SIZE(imx21->isoc_trace_failed); i++, trace++) - debug_isoc_show_one(s, "isoc failed", i, trace); - - trace = imx21->isoc_trace; - for (i = 0; i < ARRAY_SIZE(imx21->isoc_trace); i++, trace++) - debug_isoc_show_one(s, "isoc", i, trace); - - spin_unlock_irqrestore(&imx21->lock, flags); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(debug_isoc); - -static void create_debug_files(struct imx21 *imx21) -{ - struct dentry *root; - - root = debugfs_create_dir(dev_name(imx21->dev), usb_debug_root); - imx21->debug_root = root; - - debugfs_create_file("status", S_IRUGO, root, imx21, &debug_status_fops); - debugfs_create_file("dmem", S_IRUGO, root, imx21, &debug_dmem_fops); - debugfs_create_file("etd", S_IRUGO, root, imx21, &debug_etd_fops); - debugfs_create_file("statistics", S_IRUGO, root, imx21, - &debug_statistics_fops); - debugfs_create_file("isoc", S_IRUGO, root, imx21, &debug_isoc_fops); -} - -static void remove_debug_files(struct imx21 *imx21) -{ - debugfs_remove_recursive(imx21->debug_root); -} - -#endif - diff --git a/drivers/usb/host/imx21-hcd.c b/drivers/usb/host/imx21-hcd.c deleted file mode 100644 index b2716cb72471..000000000000 --- a/drivers/usb/host/imx21-hcd.c +++ /dev/null @@ -1,1933 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * USB Host Controller Driver for IMX21 - * - * Copyright (C) 2006 Loping Dog Embedded Systems - * Copyright (C) 2009 Martin Fuzzey - * Originally written by Jay Monkman - * Ported to 2.6.30, debugged and enhanced by Martin Fuzzey - */ - - - /* - * The i.MX21 USB hardware contains - * * 32 transfer descriptors (called ETDs) - * * 4Kb of Data memory - * - * The data memory is shared between the host and function controllers - * (but this driver only supports the host controller) - * - * So setting up a transfer involves: - * * Allocating a ETD - * * Fill in ETD with appropriate information - * * Allocating data memory (and putting the offset in the ETD) - * * Activate the ETD - * * Get interrupt when done. - * - * An ETD is assigned to each active endpoint. - * - * Low resource (ETD and Data memory) situations are handled differently for - * isochronous and non insosynchronous transactions : - * - * Non ISOC transfers are queued if either ETDs or Data memory are unavailable - * - * ISOC transfers use 2 ETDs per endpoint to achieve double buffering. - * They allocate both ETDs and Data memory during URB submission - * (and fail if unavailable). - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "imx21-hcd.h" - -#ifdef CONFIG_DYNAMIC_DEBUG -#define DEBUG -#endif - -#ifdef DEBUG -#define DEBUG_LOG_FRAME(imx21, etd, event) \ - (etd)->event##_frame = readl((imx21)->regs + USBH_FRMNUB) -#else -#define DEBUG_LOG_FRAME(imx21, etd, event) do { } while (0) -#endif - -static const char hcd_name[] = "imx21-hcd"; - -static inline struct imx21 *hcd_to_imx21(struct usb_hcd *hcd) -{ - return (struct imx21 *)hcd->hcd_priv; -} - - -/* =========================================== */ -/* Hardware access helpers */ -/* =========================================== */ - -static inline void set_register_bits(struct imx21 *imx21, u32 offset, u32 mask) -{ - void __iomem *reg = imx21->regs + offset; - writel(readl(reg) | mask, reg); -} - -static inline void clear_register_bits(struct imx21 *imx21, - u32 offset, u32 mask) -{ - void __iomem *reg = imx21->regs + offset; - writel(readl(reg) & ~mask, reg); -} - -static inline void clear_toggle_bit(struct imx21 *imx21, u32 offset, u32 mask) -{ - void __iomem *reg = imx21->regs + offset; - - if (readl(reg) & mask) - writel(mask, reg); -} - -static inline void set_toggle_bit(struct imx21 *imx21, u32 offset, u32 mask) -{ - void __iomem *reg = imx21->regs + offset; - - if (!(readl(reg) & mask)) - writel(mask, reg); -} - -static void etd_writel(struct imx21 *imx21, int etd_num, int dword, u32 value) -{ - writel(value, imx21->regs + USB_ETD_DWORD(etd_num, dword)); -} - -static u32 etd_readl(struct imx21 *imx21, int etd_num, int dword) -{ - return readl(imx21->regs + USB_ETD_DWORD(etd_num, dword)); -} - -static inline int wrap_frame(int counter) -{ - return counter & 0xFFFF; -} - -static inline int frame_after(int frame, int after) -{ - /* handle wrapping like jiffies time_afer */ - return (s16)((s16)after - (s16)frame) < 0; -} - -static int imx21_hc_get_frame(struct usb_hcd *hcd) -{ - struct imx21 *imx21 = hcd_to_imx21(hcd); - - return wrap_frame(readl(imx21->regs + USBH_FRMNUB)); -} - -static inline bool unsuitable_for_dma(dma_addr_t addr) -{ - return (addr & 3) != 0; -} - -#include "imx21-dbg.c" - -static void nonisoc_urb_completed_for_etd( - struct imx21 *imx21, struct etd_priv *etd, int status); -static void schedule_nonisoc_etd(struct imx21 *imx21, struct urb *urb); -static void free_dmem(struct imx21 *imx21, struct etd_priv *etd); - -/* =========================================== */ -/* ETD management */ -/* =========================================== */ - -static int alloc_etd(struct imx21 *imx21) -{ - int i; - struct etd_priv *etd = imx21->etd; - - for (i = 0; i < USB_NUM_ETD; i++, etd++) { - if (etd->alloc == 0) { - memset(etd, 0, sizeof(imx21->etd[0])); - etd->alloc = 1; - debug_etd_allocated(imx21); - return i; - } - } - return -1; -} - -static void disactivate_etd(struct imx21 *imx21, int num) -{ - int etd_mask = (1 << num); - struct etd_priv *etd = &imx21->etd[num]; - - writel(etd_mask, imx21->regs + USBH_ETDENCLR); - clear_register_bits(imx21, USBH_ETDDONEEN, etd_mask); - writel(etd_mask, imx21->regs + USB_ETDDMACHANLCLR); - clear_toggle_bit(imx21, USBH_ETDDONESTAT, etd_mask); - - etd->active_count = 0; - - DEBUG_LOG_FRAME(imx21, etd, disactivated); -} - -static void reset_etd(struct imx21 *imx21, int num) -{ - struct etd_priv *etd = imx21->etd + num; - int i; - - disactivate_etd(imx21, num); - - for (i = 0; i < 4; i++) - etd_writel(imx21, num, i, 0); - etd->urb = NULL; - etd->ep = NULL; - etd->td = NULL; - etd->bounce_buffer = NULL; -} - -static void free_etd(struct imx21 *imx21, int num) -{ - if (num < 0) - return; - - if (num >= USB_NUM_ETD) { - dev_err(imx21->dev, "BAD etd=%d!\n", num); - return; - } - if (imx21->etd[num].alloc == 0) { - dev_err(imx21->dev, "ETD %d already free!\n", num); - return; - } - - debug_etd_freed(imx21); - reset_etd(imx21, num); - memset(&imx21->etd[num], 0, sizeof(imx21->etd[0])); -} - - -static void setup_etd_dword0(struct imx21 *imx21, - int etd_num, struct urb *urb, u8 dir, u16 maxpacket) -{ - etd_writel(imx21, etd_num, 0, - ((u32) usb_pipedevice(urb->pipe)) << DW0_ADDRESS | - ((u32) usb_pipeendpoint(urb->pipe) << DW0_ENDPNT) | - ((u32) dir << DW0_DIRECT) | - ((u32) ((urb->dev->speed == USB_SPEED_LOW) ? - 1 : 0) << DW0_SPEED) | - ((u32) fmt_urb_to_etd[usb_pipetype(urb->pipe)] << DW0_FORMAT) | - ((u32) maxpacket << DW0_MAXPKTSIZ)); -} - -/* - * Copy buffer to data controller data memory. - * We cannot use memcpy_toio() because the hardware requires 32bit writes - */ -static void copy_to_dmem( - struct imx21 *imx21, int dmem_offset, void *src, int count) -{ - void __iomem *dmem = imx21->regs + USBOTG_DMEM + dmem_offset; - u32 word = 0; - u8 *p = src; - int byte = 0; - int i; - - for (i = 0; i < count; i++) { - byte = i % 4; - word += (*p++ << (byte * 8)); - if (byte == 3) { - writel(word, dmem); - dmem += 4; - word = 0; - } - } - - if (count && byte != 3) - writel(word, dmem); -} - -static void activate_etd(struct imx21 *imx21, int etd_num, u8 dir) -{ - u32 etd_mask = 1 << etd_num; - struct etd_priv *etd = &imx21->etd[etd_num]; - - if (etd->dma_handle && unsuitable_for_dma(etd->dma_handle)) { - /* For non aligned isoc the condition below is always true */ - if (etd->len <= etd->dmem_size) { - /* Fits into data memory, use PIO */ - if (dir != TD_DIR_IN) { - copy_to_dmem(imx21, - etd->dmem_offset, - etd->cpu_buffer, etd->len); - } - etd->dma_handle = 0; - - } else { - /* Too big for data memory, use bounce buffer */ - enum dma_data_direction dmadir; - - if (dir == TD_DIR_IN) { - dmadir = DMA_FROM_DEVICE; - etd->bounce_buffer = kmalloc(etd->len, - GFP_ATOMIC); - } else { - dmadir = DMA_TO_DEVICE; - etd->bounce_buffer = kmemdup(etd->cpu_buffer, - etd->len, - GFP_ATOMIC); - } - if (!etd->bounce_buffer) { - dev_err(imx21->dev, "failed bounce alloc\n"); - goto err_bounce_alloc; - } - - etd->dma_handle = - dma_map_single(imx21->dev, - etd->bounce_buffer, - etd->len, - dmadir); - if (dma_mapping_error(imx21->dev, etd->dma_handle)) { - dev_err(imx21->dev, "failed bounce map\n"); - goto err_bounce_map; - } - } - } - - clear_toggle_bit(imx21, USBH_ETDDONESTAT, etd_mask); - set_register_bits(imx21, USBH_ETDDONEEN, etd_mask); - clear_toggle_bit(imx21, USBH_XFILLSTAT, etd_mask); - clear_toggle_bit(imx21, USBH_YFILLSTAT, etd_mask); - - if (etd->dma_handle) { - set_register_bits(imx21, USB_ETDDMACHANLCLR, etd_mask); - clear_toggle_bit(imx21, USBH_XBUFSTAT, etd_mask); - clear_toggle_bit(imx21, USBH_YBUFSTAT, etd_mask); - writel(etd->dma_handle, imx21->regs + USB_ETDSMSA(etd_num)); - set_register_bits(imx21, USB_ETDDMAEN, etd_mask); - } else { - if (dir != TD_DIR_IN) { - /* need to set for ZLP and PIO */ - set_toggle_bit(imx21, USBH_XFILLSTAT, etd_mask); - set_toggle_bit(imx21, USBH_YFILLSTAT, etd_mask); - } - } - - DEBUG_LOG_FRAME(imx21, etd, activated); - -#ifdef DEBUG - if (!etd->active_count) { - int i; - etd->activated_frame = readl(imx21->regs + USBH_FRMNUB); - etd->disactivated_frame = -1; - etd->last_int_frame = -1; - etd->last_req_frame = -1; - - for (i = 0; i < 4; i++) - etd->submitted_dwords[i] = etd_readl(imx21, etd_num, i); - } -#endif - - etd->active_count = 1; - writel(etd_mask, imx21->regs + USBH_ETDENSET); - return; - -err_bounce_map: - kfree(etd->bounce_buffer); - -err_bounce_alloc: - free_dmem(imx21, etd); - nonisoc_urb_completed_for_etd(imx21, etd, -ENOMEM); -} - -/* =========================================== */ -/* Data memory management */ -/* =========================================== */ - -static int alloc_dmem(struct imx21 *imx21, unsigned int size, - struct usb_host_endpoint *ep) -{ - unsigned int offset = 0; - struct imx21_dmem_area *area; - struct imx21_dmem_area *tmp; - - size += (~size + 1) & 0x3; /* Round to 4 byte multiple */ - - if (size > DMEM_SIZE) { - dev_err(imx21->dev, "size=%d > DMEM_SIZE(%d)\n", - size, DMEM_SIZE); - return -EINVAL; - } - - list_for_each_entry(tmp, &imx21->dmem_list, list) { - if ((size + offset) < offset) - goto fail; - if ((size + offset) <= tmp->offset) - break; - offset = tmp->size + tmp->offset; - if ((offset + size) > DMEM_SIZE) - goto fail; - } - - area = kmalloc(sizeof(struct imx21_dmem_area), GFP_ATOMIC); - if (area == NULL) - return -ENOMEM; - - area->ep = ep; - area->offset = offset; - area->size = size; - list_add_tail(&area->list, &tmp->list); - debug_dmem_allocated(imx21, size); - return offset; - -fail: - return -ENOMEM; -} - -/* Memory now available for a queued ETD - activate it */ -static void activate_queued_etd(struct imx21 *imx21, - struct etd_priv *etd, u32 dmem_offset) -{ - struct urb_priv *urb_priv = etd->urb->hcpriv; - int etd_num = etd - &imx21->etd[0]; - u32 maxpacket = etd_readl(imx21, etd_num, 1) >> DW1_YBUFSRTAD; - u8 dir = (etd_readl(imx21, etd_num, 2) >> DW2_DIRPID) & 0x03; - - dev_dbg(imx21->dev, "activating queued ETD %d now DMEM available\n", - etd_num); - etd_writel(imx21, etd_num, 1, - ((dmem_offset + maxpacket) << DW1_YBUFSRTAD) | dmem_offset); - - etd->dmem_offset = dmem_offset; - urb_priv->active = 1; - activate_etd(imx21, etd_num, dir); -} - -static void free_dmem(struct imx21 *imx21, struct etd_priv *etd) -{ - struct imx21_dmem_area *area; - struct etd_priv *tmp; - int found = 0; - int offset; - - if (!etd->dmem_size) - return; - etd->dmem_size = 0; - - offset = etd->dmem_offset; - list_for_each_entry(area, &imx21->dmem_list, list) { - if (area->offset == offset) { - debug_dmem_freed(imx21, area->size); - list_del(&area->list); - kfree(area); - found = 1; - break; - } - } - - if (!found) { - dev_err(imx21->dev, - "Trying to free unallocated DMEM %d\n", offset); - return; - } - - /* Try again to allocate memory for anything we've queued */ - list_for_each_entry_safe(etd, tmp, &imx21->queue_for_dmem, queue) { - offset = alloc_dmem(imx21, etd->dmem_size, etd->ep); - if (offset >= 0) { - list_del(&etd->queue); - activate_queued_etd(imx21, etd, (u32)offset); - } - } -} - -static void free_epdmem(struct imx21 *imx21, struct usb_host_endpoint *ep) -{ - struct imx21_dmem_area *area, *tmp; - - list_for_each_entry_safe(area, tmp, &imx21->dmem_list, list) { - if (area->ep == ep) { - dev_err(imx21->dev, - "Active DMEM %d for disabled ep=%p\n", - area->offset, ep); - list_del(&area->list); - kfree(area); - } - } -} - - -/* =========================================== */ -/* End handling */ -/* =========================================== */ - -/* Endpoint now idle - release its ETD(s) or assign to queued request */ -static void ep_idle(struct imx21 *imx21, struct ep_priv *ep_priv) -{ - int i; - - for (i = 0; i < NUM_ISO_ETDS; i++) { - int etd_num = ep_priv->etd[i]; - struct etd_priv *etd; - if (etd_num < 0) - continue; - - etd = &imx21->etd[etd_num]; - ep_priv->etd[i] = -1; - - free_dmem(imx21, etd); /* for isoc */ - - if (list_empty(&imx21->queue_for_etd)) { - free_etd(imx21, etd_num); - continue; - } - - dev_dbg(imx21->dev, - "assigning idle etd %d for queued request\n", etd_num); - ep_priv = list_first_entry(&imx21->queue_for_etd, - struct ep_priv, queue); - list_del(&ep_priv->queue); - reset_etd(imx21, etd_num); - ep_priv->waiting_etd = 0; - ep_priv->etd[i] = etd_num; - - if (list_empty(&ep_priv->ep->urb_list)) { - dev_err(imx21->dev, "No urb for queued ep!\n"); - continue; - } - schedule_nonisoc_etd(imx21, list_first_entry( - &ep_priv->ep->urb_list, struct urb, urb_list)); - } -} - -static void urb_done(struct usb_hcd *hcd, struct urb *urb, int status) -__releases(imx21->lock) -__acquires(imx21->lock) -{ - struct imx21 *imx21 = hcd_to_imx21(hcd); - struct ep_priv *ep_priv = urb->ep->hcpriv; - struct urb_priv *urb_priv = urb->hcpriv; - - debug_urb_completed(imx21, urb, status); - dev_vdbg(imx21->dev, "urb %p done %d\n", urb, status); - - kfree(urb_priv->isoc_td); - kfree(urb->hcpriv); - urb->hcpriv = NULL; - usb_hcd_unlink_urb_from_ep(hcd, urb); - spin_unlock(&imx21->lock); - usb_hcd_giveback_urb(hcd, urb, status); - spin_lock(&imx21->lock); - if (list_empty(&ep_priv->ep->urb_list)) - ep_idle(imx21, ep_priv); -} - -static void nonisoc_urb_completed_for_etd( - struct imx21 *imx21, struct etd_priv *etd, int status) -{ - struct usb_host_endpoint *ep = etd->ep; - - urb_done(imx21->hcd, etd->urb, status); - etd->urb = NULL; - - if (!list_empty(&ep->urb_list)) { - struct urb *urb = list_first_entry( - &ep->urb_list, struct urb, urb_list); - - dev_vdbg(imx21->dev, "next URB %p\n", urb); - schedule_nonisoc_etd(imx21, urb); - } -} - - -/* =========================================== */ -/* ISOC Handling ... */ -/* =========================================== */ - -static void schedule_isoc_etds(struct usb_hcd *hcd, - struct usb_host_endpoint *ep) -{ - struct imx21 *imx21 = hcd_to_imx21(hcd); - struct ep_priv *ep_priv = ep->hcpriv; - struct etd_priv *etd; - struct urb_priv *urb_priv; - struct td *td; - int etd_num; - int i; - int cur_frame; - u8 dir; - - for (i = 0; i < NUM_ISO_ETDS; i++) { -too_late: - if (list_empty(&ep_priv->td_list)) - break; - - etd_num = ep_priv->etd[i]; - if (etd_num < 0) - break; - - etd = &imx21->etd[etd_num]; - if (etd->urb) - continue; - - td = list_entry(ep_priv->td_list.next, struct td, list); - list_del(&td->list); - urb_priv = td->urb->hcpriv; - - cur_frame = imx21_hc_get_frame(hcd); - if (frame_after(cur_frame, td->frame)) { - dev_dbg(imx21->dev, "isoc too late frame %d > %d\n", - cur_frame, td->frame); - urb_priv->isoc_status = -EXDEV; - td->urb->iso_frame_desc[ - td->isoc_index].actual_length = 0; - td->urb->iso_frame_desc[td->isoc_index].status = -EXDEV; - if (--urb_priv->isoc_remaining == 0) - urb_done(hcd, td->urb, urb_priv->isoc_status); - goto too_late; - } - - urb_priv->active = 1; - etd->td = td; - etd->ep = td->ep; - etd->urb = td->urb; - etd->len = td->len; - etd->dma_handle = td->dma_handle; - etd->cpu_buffer = td->cpu_buffer; - - debug_isoc_submitted(imx21, cur_frame, td); - - dir = usb_pipeout(td->urb->pipe) ? TD_DIR_OUT : TD_DIR_IN; - setup_etd_dword0(imx21, etd_num, td->urb, dir, etd->dmem_size); - etd_writel(imx21, etd_num, 1, etd->dmem_offset); - etd_writel(imx21, etd_num, 2, - (TD_NOTACCESSED << DW2_COMPCODE) | - ((td->frame & 0xFFFF) << DW2_STARTFRM)); - etd_writel(imx21, etd_num, 3, - (TD_NOTACCESSED << DW3_COMPCODE0) | - (td->len << DW3_PKTLEN0)); - - activate_etd(imx21, etd_num, dir); - } -} - -static void isoc_etd_done(struct usb_hcd *hcd, int etd_num) -{ - struct imx21 *imx21 = hcd_to_imx21(hcd); - int etd_mask = 1 << etd_num; - struct etd_priv *etd = imx21->etd + etd_num; - struct urb *urb = etd->urb; - struct urb_priv *urb_priv = urb->hcpriv; - struct td *td = etd->td; - struct usb_host_endpoint *ep = etd->ep; - int isoc_index = td->isoc_index; - unsigned int pipe = urb->pipe; - int dir_in = usb_pipein(pipe); - int cc; - int bytes_xfrd; - - disactivate_etd(imx21, etd_num); - - cc = (etd_readl(imx21, etd_num, 3) >> DW3_COMPCODE0) & 0xf; - bytes_xfrd = etd_readl(imx21, etd_num, 3) & 0x3ff; - - /* Input doesn't always fill the buffer, don't generate an error - * when this happens. - */ - if (dir_in && (cc == TD_DATAUNDERRUN)) - cc = TD_CC_NOERROR; - - if (cc == TD_NOTACCESSED) - bytes_xfrd = 0; - - debug_isoc_completed(imx21, - imx21_hc_get_frame(hcd), td, cc, bytes_xfrd); - if (cc) { - urb_priv->isoc_status = -EXDEV; - dev_dbg(imx21->dev, - "bad iso cc=0x%X frame=%d sched frame=%d " - "cnt=%d len=%d urb=%p etd=%d index=%d\n", - cc, imx21_hc_get_frame(hcd), td->frame, - bytes_xfrd, td->len, urb, etd_num, isoc_index); - } - - if (dir_in) { - clear_toggle_bit(imx21, USBH_XFILLSTAT, etd_mask); - if (!etd->dma_handle) - memcpy_fromio(etd->cpu_buffer, - imx21->regs + USBOTG_DMEM + etd->dmem_offset, - bytes_xfrd); - } - - urb->actual_length += bytes_xfrd; - urb->iso_frame_desc[isoc_index].actual_length = bytes_xfrd; - urb->iso_frame_desc[isoc_index].status = cc_to_error[cc]; - - etd->td = NULL; - etd->urb = NULL; - etd->ep = NULL; - - if (--urb_priv->isoc_remaining == 0) - urb_done(hcd, urb, urb_priv->isoc_status); - - schedule_isoc_etds(hcd, ep); -} - -static struct ep_priv *alloc_isoc_ep( - struct imx21 *imx21, struct usb_host_endpoint *ep) -{ - struct ep_priv *ep_priv; - int i; - - ep_priv = kzalloc(sizeof(struct ep_priv), GFP_ATOMIC); - if (!ep_priv) - return NULL; - - for (i = 0; i < NUM_ISO_ETDS; i++) - ep_priv->etd[i] = -1; - - INIT_LIST_HEAD(&ep_priv->td_list); - ep_priv->ep = ep; - ep->hcpriv = ep_priv; - return ep_priv; -} - -static int alloc_isoc_etds(struct imx21 *imx21, struct ep_priv *ep_priv) -{ - int i, j; - int etd_num; - - /* Allocate the ETDs if required */ - for (i = 0; i < NUM_ISO_ETDS; i++) { - if (ep_priv->etd[i] < 0) { - etd_num = alloc_etd(imx21); - if (etd_num < 0) - goto alloc_etd_failed; - - ep_priv->etd[i] = etd_num; - imx21->etd[etd_num].ep = ep_priv->ep; - } - } - return 0; - -alloc_etd_failed: - dev_err(imx21->dev, "isoc: Couldn't allocate etd\n"); - for (j = 0; j < i; j++) { - free_etd(imx21, ep_priv->etd[j]); - ep_priv->etd[j] = -1; - } - return -ENOMEM; -} - -static int imx21_hc_urb_enqueue_isoc(struct usb_hcd *hcd, - struct usb_host_endpoint *ep, - struct urb *urb, gfp_t mem_flags) -{ - struct imx21 *imx21 = hcd_to_imx21(hcd); - struct urb_priv *urb_priv; - unsigned long flags; - struct ep_priv *ep_priv; - struct td *td = NULL; - int i; - int ret; - int cur_frame; - u16 maxpacket; - - urb_priv = kzalloc(sizeof(struct urb_priv), mem_flags); - if (urb_priv == NULL) - return -ENOMEM; - - urb_priv->isoc_td = kcalloc(urb->number_of_packets, sizeof(struct td), - mem_flags); - if (urb_priv->isoc_td == NULL) { - ret = -ENOMEM; - goto alloc_td_failed; - } - - spin_lock_irqsave(&imx21->lock, flags); - - if (ep->hcpriv == NULL) { - ep_priv = alloc_isoc_ep(imx21, ep); - if (ep_priv == NULL) { - ret = -ENOMEM; - goto alloc_ep_failed; - } - } else { - ep_priv = ep->hcpriv; - } - - ret = alloc_isoc_etds(imx21, ep_priv); - if (ret) - goto alloc_etd_failed; - - ret = usb_hcd_link_urb_to_ep(hcd, urb); - if (ret) - goto link_failed; - - urb->status = -EINPROGRESS; - urb->actual_length = 0; - urb->error_count = 0; - urb->hcpriv = urb_priv; - urb_priv->ep = ep; - - /* allocate data memory for largest packets if not already done */ - maxpacket = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)); - for (i = 0; i < NUM_ISO_ETDS; i++) { - struct etd_priv *etd = &imx21->etd[ep_priv->etd[i]]; - - if (etd->dmem_size > 0 && etd->dmem_size < maxpacket) { - /* not sure if this can really occur.... */ - dev_err(imx21->dev, "increasing isoc buffer %d->%d\n", - etd->dmem_size, maxpacket); - ret = -EMSGSIZE; - goto alloc_dmem_failed; - } - - if (etd->dmem_size == 0) { - etd->dmem_offset = alloc_dmem(imx21, maxpacket, ep); - if (etd->dmem_offset < 0) { - dev_dbg(imx21->dev, "failed alloc isoc dmem\n"); - ret = -EAGAIN; - goto alloc_dmem_failed; - } - etd->dmem_size = maxpacket; - } - } - - /* calculate frame */ - cur_frame = imx21_hc_get_frame(hcd); - i = 0; - if (list_empty(&ep_priv->td_list)) { - urb->start_frame = wrap_frame(cur_frame + 5); - } else { - urb->start_frame = wrap_frame(list_entry(ep_priv->td_list.prev, - struct td, list)->frame + urb->interval); - - if (frame_after(cur_frame, urb->start_frame)) { - dev_dbg(imx21->dev, - "enqueue: adjusting iso start %d (cur=%d) asap=%d\n", - urb->start_frame, cur_frame, - (urb->transfer_flags & URB_ISO_ASAP) != 0); - i = DIV_ROUND_UP(wrap_frame( - cur_frame - urb->start_frame), - urb->interval); - - /* Treat underruns as if URB_ISO_ASAP was set */ - if ((urb->transfer_flags & URB_ISO_ASAP) || - i >= urb->number_of_packets) { - urb->start_frame = wrap_frame(urb->start_frame - + i * urb->interval); - i = 0; - } - } - } - - /* set up transfers */ - urb_priv->isoc_remaining = urb->number_of_packets - i; - td = urb_priv->isoc_td; - for (; i < urb->number_of_packets; i++, td++) { - unsigned int offset = urb->iso_frame_desc[i].offset; - td->ep = ep; - td->urb = urb; - td->len = urb->iso_frame_desc[i].length; - td->isoc_index = i; - td->frame = wrap_frame(urb->start_frame + urb->interval * i); - td->dma_handle = urb->transfer_dma + offset; - td->cpu_buffer = urb->transfer_buffer + offset; - list_add_tail(&td->list, &ep_priv->td_list); - } - - dev_vdbg(imx21->dev, "setup %d packets for iso frame %d->%d\n", - urb->number_of_packets, urb->start_frame, td->frame); - - debug_urb_submitted(imx21, urb); - schedule_isoc_etds(hcd, ep); - - spin_unlock_irqrestore(&imx21->lock, flags); - return 0; - -alloc_dmem_failed: - usb_hcd_unlink_urb_from_ep(hcd, urb); - -link_failed: -alloc_etd_failed: -alloc_ep_failed: - spin_unlock_irqrestore(&imx21->lock, flags); - kfree(urb_priv->isoc_td); - -alloc_td_failed: - kfree(urb_priv); - return ret; -} - -static void dequeue_isoc_urb(struct imx21 *imx21, - struct urb *urb, struct ep_priv *ep_priv) -{ - struct urb_priv *urb_priv = urb->hcpriv; - struct td *td, *tmp; - int i; - - if (urb_priv->active) { - for (i = 0; i < NUM_ISO_ETDS; i++) { - int etd_num = ep_priv->etd[i]; - if (etd_num != -1 && imx21->etd[etd_num].urb == urb) { - struct etd_priv *etd = imx21->etd + etd_num; - - reset_etd(imx21, etd_num); - free_dmem(imx21, etd); - } - } - } - - list_for_each_entry_safe(td, tmp, &ep_priv->td_list, list) { - if (td->urb == urb) { - dev_vdbg(imx21->dev, "removing td %p\n", td); - list_del(&td->list); - } - } -} - -/* =========================================== */ -/* NON ISOC Handling ... */ -/* =========================================== */ - -static void schedule_nonisoc_etd(struct imx21 *imx21, struct urb *urb) -{ - unsigned int pipe = urb->pipe; - struct urb_priv *urb_priv = urb->hcpriv; - struct ep_priv *ep_priv = urb_priv->ep->hcpriv; - int state = urb_priv->state; - int etd_num = ep_priv->etd[0]; - struct etd_priv *etd; - u32 count; - u16 etd_buf_size; - u16 maxpacket; - u8 dir; - u8 bufround; - u8 datatoggle; - u8 interval = 0; - u8 relpolpos = 0; - - if (etd_num < 0) { - dev_err(imx21->dev, "No valid ETD\n"); - return; - } - if (readl(imx21->regs + USBH_ETDENSET) & (1 << etd_num)) - dev_err(imx21->dev, "submitting to active ETD %d\n", etd_num); - - etd = &imx21->etd[etd_num]; - maxpacket = usb_maxpacket(urb->dev, pipe, usb_pipeout(pipe)); - if (!maxpacket) - maxpacket = 8; - - if (usb_pipecontrol(pipe) && (state != US_CTRL_DATA)) { - if (state == US_CTRL_SETUP) { - dir = TD_DIR_SETUP; - if (unsuitable_for_dma(urb->setup_dma)) - usb_hcd_unmap_urb_setup_for_dma(imx21->hcd, - urb); - etd->dma_handle = urb->setup_dma; - etd->cpu_buffer = urb->setup_packet; - bufround = 0; - count = 8; - datatoggle = TD_TOGGLE_DATA0; - } else { /* US_CTRL_ACK */ - dir = usb_pipeout(pipe) ? TD_DIR_IN : TD_DIR_OUT; - bufround = 0; - count = 0; - datatoggle = TD_TOGGLE_DATA1; - } - } else { - dir = usb_pipeout(pipe) ? TD_DIR_OUT : TD_DIR_IN; - bufround = (dir == TD_DIR_IN) ? 1 : 0; - if (unsuitable_for_dma(urb->transfer_dma)) - usb_hcd_unmap_urb_for_dma(imx21->hcd, urb); - - etd->dma_handle = urb->transfer_dma; - etd->cpu_buffer = urb->transfer_buffer; - if (usb_pipebulk(pipe) && (state == US_BULK0)) - count = 0; - else - count = urb->transfer_buffer_length; - - if (usb_pipecontrol(pipe)) { - datatoggle = TD_TOGGLE_DATA1; - } else { - if (usb_gettoggle( - urb->dev, - usb_pipeendpoint(urb->pipe), - usb_pipeout(urb->pipe))) - datatoggle = TD_TOGGLE_DATA1; - else - datatoggle = TD_TOGGLE_DATA0; - } - } - - etd->urb = urb; - etd->ep = urb_priv->ep; - etd->len = count; - - if (usb_pipeint(pipe)) { - interval = urb->interval; - relpolpos = (readl(imx21->regs + USBH_FRMNUB) + 1) & 0xff; - } - - /* Write ETD to device memory */ - setup_etd_dword0(imx21, etd_num, urb, dir, maxpacket); - - etd_writel(imx21, etd_num, 2, - (u32) interval << DW2_POLINTERV | - ((u32) relpolpos << DW2_RELPOLPOS) | - ((u32) dir << DW2_DIRPID) | - ((u32) bufround << DW2_BUFROUND) | - ((u32) datatoggle << DW2_DATATOG) | - ((u32) TD_NOTACCESSED << DW2_COMPCODE)); - - /* DMA will always transfer buffer size even if TOBYCNT in DWORD3 - is smaller. Make sure we don't overrun the buffer! - */ - if (count && count < maxpacket) - etd_buf_size = count; - else - etd_buf_size = maxpacket; - - etd_writel(imx21, etd_num, 3, - ((u32) (etd_buf_size - 1) << DW3_BUFSIZE) | (u32) count); - - if (!count) - etd->dma_handle = 0; - - /* allocate x and y buffer space at once */ - etd->dmem_size = (count > maxpacket) ? maxpacket * 2 : maxpacket; - etd->dmem_offset = alloc_dmem(imx21, etd->dmem_size, urb_priv->ep); - if (etd->dmem_offset < 0) { - /* Setup everything we can in HW and update when we get DMEM */ - etd_writel(imx21, etd_num, 1, (u32)maxpacket << 16); - - dev_dbg(imx21->dev, "Queuing etd %d for DMEM\n", etd_num); - debug_urb_queued_for_dmem(imx21, urb); - list_add_tail(&etd->queue, &imx21->queue_for_dmem); - return; - } - - etd_writel(imx21, etd_num, 1, - (((u32) etd->dmem_offset + (u32) maxpacket) << DW1_YBUFSRTAD) | - (u32) etd->dmem_offset); - - urb_priv->active = 1; - - /* enable the ETD to kick off transfer */ - dev_vdbg(imx21->dev, "Activating etd %d for %d bytes %s\n", - etd_num, count, dir != TD_DIR_IN ? "out" : "in"); - activate_etd(imx21, etd_num, dir); - -} - -static void nonisoc_etd_done(struct usb_hcd *hcd, int etd_num) -{ - struct imx21 *imx21 = hcd_to_imx21(hcd); - struct etd_priv *etd = &imx21->etd[etd_num]; - struct urb *urb = etd->urb; - u32 etd_mask = 1 << etd_num; - struct urb_priv *urb_priv = urb->hcpriv; - int dir; - int cc; - u32 bytes_xfrd; - int etd_done; - - disactivate_etd(imx21, etd_num); - - dir = (etd_readl(imx21, etd_num, 0) >> DW0_DIRECT) & 0x3; - cc = (etd_readl(imx21, etd_num, 2) >> DW2_COMPCODE) & 0xf; - bytes_xfrd = etd->len - (etd_readl(imx21, etd_num, 3) & 0x1fffff); - - /* save toggle carry */ - usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), - usb_pipeout(urb->pipe), - (etd_readl(imx21, etd_num, 0) >> DW0_TOGCRY) & 0x1); - - if (dir == TD_DIR_IN) { - clear_toggle_bit(imx21, USBH_XFILLSTAT, etd_mask); - clear_toggle_bit(imx21, USBH_YFILLSTAT, etd_mask); - - if (etd->bounce_buffer) { - memcpy(etd->cpu_buffer, etd->bounce_buffer, bytes_xfrd); - dma_unmap_single(imx21->dev, - etd->dma_handle, etd->len, DMA_FROM_DEVICE); - } else if (!etd->dma_handle && bytes_xfrd) {/* PIO */ - memcpy_fromio(etd->cpu_buffer, - imx21->regs + USBOTG_DMEM + etd->dmem_offset, - bytes_xfrd); - } - } - - kfree(etd->bounce_buffer); - etd->bounce_buffer = NULL; - free_dmem(imx21, etd); - - urb->error_count = 0; - if (!(urb->transfer_flags & URB_SHORT_NOT_OK) - && (cc == TD_DATAUNDERRUN)) - cc = TD_CC_NOERROR; - - if (cc != 0) - dev_vdbg(imx21->dev, "cc is 0x%x\n", cc); - - etd_done = (cc_to_error[cc] != 0); /* stop if error */ - - switch (usb_pipetype(urb->pipe)) { - case PIPE_CONTROL: - switch (urb_priv->state) { - case US_CTRL_SETUP: - if (urb->transfer_buffer_length > 0) - urb_priv->state = US_CTRL_DATA; - else - urb_priv->state = US_CTRL_ACK; - break; - case US_CTRL_DATA: - urb->actual_length += bytes_xfrd; - urb_priv->state = US_CTRL_ACK; - break; - case US_CTRL_ACK: - etd_done = 1; - break; - default: - dev_err(imx21->dev, - "Invalid pipe state %d\n", urb_priv->state); - etd_done = 1; - break; - } - break; - - case PIPE_BULK: - urb->actual_length += bytes_xfrd; - if ((urb_priv->state == US_BULK) - && (urb->transfer_flags & URB_ZERO_PACKET) - && urb->transfer_buffer_length > 0 - && ((urb->transfer_buffer_length % - usb_maxpacket(urb->dev, urb->pipe, - usb_pipeout(urb->pipe))) == 0)) { - /* need a 0-packet */ - urb_priv->state = US_BULK0; - } else { - etd_done = 1; - } - break; - - case PIPE_INTERRUPT: - urb->actual_length += bytes_xfrd; - etd_done = 1; - break; - } - - if (etd_done) - nonisoc_urb_completed_for_etd(imx21, etd, cc_to_error[cc]); - else { - dev_vdbg(imx21->dev, "next state=%d\n", urb_priv->state); - schedule_nonisoc_etd(imx21, urb); - } -} - - -static struct ep_priv *alloc_ep(void) -{ - int i; - struct ep_priv *ep_priv; - - ep_priv = kzalloc(sizeof(struct ep_priv), GFP_ATOMIC); - if (!ep_priv) - return NULL; - - for (i = 0; i < NUM_ISO_ETDS; ++i) - ep_priv->etd[i] = -1; - - return ep_priv; -} - -static int imx21_hc_urb_enqueue(struct usb_hcd *hcd, - struct urb *urb, gfp_t mem_flags) -{ - struct imx21 *imx21 = hcd_to_imx21(hcd); - struct usb_host_endpoint *ep = urb->ep; - struct urb_priv *urb_priv; - struct ep_priv *ep_priv; - struct etd_priv *etd; - int ret; - unsigned long flags; - - dev_vdbg(imx21->dev, - "enqueue urb=%p ep=%p len=%d " - "buffer=%p dma=%pad setupBuf=%p setupDma=%pad\n", - urb, ep, - urb->transfer_buffer_length, - urb->transfer_buffer, &urb->transfer_dma, - urb->setup_packet, &urb->setup_dma); - - if (usb_pipeisoc(urb->pipe)) - return imx21_hc_urb_enqueue_isoc(hcd, ep, urb, mem_flags); - - urb_priv = kzalloc(sizeof(struct urb_priv), mem_flags); - if (!urb_priv) - return -ENOMEM; - - spin_lock_irqsave(&imx21->lock, flags); - - ep_priv = ep->hcpriv; - if (ep_priv == NULL) { - ep_priv = alloc_ep(); - if (!ep_priv) { - ret = -ENOMEM; - goto failed_alloc_ep; - } - ep->hcpriv = ep_priv; - ep_priv->ep = ep; - } - - ret = usb_hcd_link_urb_to_ep(hcd, urb); - if (ret) - goto failed_link; - - urb->status = -EINPROGRESS; - urb->actual_length = 0; - urb->error_count = 0; - urb->hcpriv = urb_priv; - urb_priv->ep = ep; - - switch (usb_pipetype(urb->pipe)) { - case PIPE_CONTROL: - urb_priv->state = US_CTRL_SETUP; - break; - case PIPE_BULK: - urb_priv->state = US_BULK; - break; - } - - debug_urb_submitted(imx21, urb); - if (ep_priv->etd[0] < 0) { - if (ep_priv->waiting_etd) { - dev_dbg(imx21->dev, - "no ETD available already queued %p\n", - ep_priv); - debug_urb_queued_for_etd(imx21, urb); - goto out; - } - ep_priv->etd[0] = alloc_etd(imx21); - if (ep_priv->etd[0] < 0) { - dev_dbg(imx21->dev, - "no ETD available queueing %p\n", ep_priv); - debug_urb_queued_for_etd(imx21, urb); - list_add_tail(&ep_priv->queue, &imx21->queue_for_etd); - ep_priv->waiting_etd = 1; - goto out; - } - } - - /* Schedule if no URB already active for this endpoint */ - etd = &imx21->etd[ep_priv->etd[0]]; - if (etd->urb == NULL) { - DEBUG_LOG_FRAME(imx21, etd, last_req); - schedule_nonisoc_etd(imx21, urb); - } - -out: - spin_unlock_irqrestore(&imx21->lock, flags); - return 0; - -failed_link: -failed_alloc_ep: - spin_unlock_irqrestore(&imx21->lock, flags); - kfree(urb_priv); - return ret; -} - -static int imx21_hc_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, - int status) -{ - struct imx21 *imx21 = hcd_to_imx21(hcd); - unsigned long flags; - struct usb_host_endpoint *ep; - struct ep_priv *ep_priv; - struct urb_priv *urb_priv = urb->hcpriv; - int ret = -EINVAL; - - dev_vdbg(imx21->dev, "dequeue urb=%p iso=%d status=%d\n", - urb, usb_pipeisoc(urb->pipe), status); - - spin_lock_irqsave(&imx21->lock, flags); - - ret = usb_hcd_check_unlink_urb(hcd, urb, status); - if (ret) - goto fail; - ep = urb_priv->ep; - ep_priv = ep->hcpriv; - - debug_urb_unlinked(imx21, urb); - - if (usb_pipeisoc(urb->pipe)) { - dequeue_isoc_urb(imx21, urb, ep_priv); - schedule_isoc_etds(hcd, ep); - } else if (urb_priv->active) { - int etd_num = ep_priv->etd[0]; - if (etd_num != -1) { - struct etd_priv *etd = &imx21->etd[etd_num]; - - disactivate_etd(imx21, etd_num); - free_dmem(imx21, etd); - etd->urb = NULL; - kfree(etd->bounce_buffer); - etd->bounce_buffer = NULL; - } - } - - urb_done(hcd, urb, status); - - spin_unlock_irqrestore(&imx21->lock, flags); - return 0; - -fail: - spin_unlock_irqrestore(&imx21->lock, flags); - return ret; -} - -/* =========================================== */ -/* Interrupt dispatch */ -/* =========================================== */ - -static void process_etds(struct usb_hcd *hcd, struct imx21 *imx21, int sof) -{ - int etd_num; - int enable_sof_int = 0; - unsigned long flags; - - spin_lock_irqsave(&imx21->lock, flags); - - for (etd_num = 0; etd_num < USB_NUM_ETD; etd_num++) { - u32 etd_mask = 1 << etd_num; - u32 enabled = readl(imx21->regs + USBH_ETDENSET) & etd_mask; - u32 done = readl(imx21->regs + USBH_ETDDONESTAT) & etd_mask; - struct etd_priv *etd = &imx21->etd[etd_num]; - - - if (done) { - DEBUG_LOG_FRAME(imx21, etd, last_int); - } else { -/* - * Kludge warning! - * - * When multiple transfers are using the bus we sometimes get into a state - * where the transfer has completed (the CC field of the ETD is != 0x0F), - * the ETD has self disabled but the ETDDONESTAT flag is not set - * (and hence no interrupt occurs). - * This causes the transfer in question to hang. - * The kludge below checks for this condition at each SOF and processes any - * blocked ETDs (after an arbitrary 10 frame wait) - * - * With a single active transfer the usbtest test suite will run for days - * without the kludge. - * With other bus activity (eg mass storage) even just test1 will hang without - * the kludge. - */ - u32 dword0; - int cc; - - if (etd->active_count && !enabled) /* suspicious... */ - enable_sof_int = 1; - - if (!sof || enabled || !etd->active_count) - continue; - - cc = etd_readl(imx21, etd_num, 2) >> DW2_COMPCODE; - if (cc == TD_NOTACCESSED) - continue; - - if (++etd->active_count < 10) - continue; - - dword0 = etd_readl(imx21, etd_num, 0); - dev_dbg(imx21->dev, - "unblock ETD %d dev=0x%X ep=0x%X cc=0x%02X!\n", - etd_num, dword0 & 0x7F, - (dword0 >> DW0_ENDPNT) & 0x0F, - cc); - -#ifdef DEBUG - dev_dbg(imx21->dev, - "frame: act=%d disact=%d" - " int=%d req=%d cur=%d\n", - etd->activated_frame, - etd->disactivated_frame, - etd->last_int_frame, - etd->last_req_frame, - readl(imx21->regs + USBH_FRMNUB)); - imx21->debug_unblocks++; -#endif - etd->active_count = 0; -/* End of kludge */ - } - - if (etd->ep == NULL || etd->urb == NULL) { - dev_dbg(imx21->dev, - "Interrupt for unexpected etd %d" - " ep=%p urb=%p\n", - etd_num, etd->ep, etd->urb); - disactivate_etd(imx21, etd_num); - continue; - } - - if (usb_pipeisoc(etd->urb->pipe)) - isoc_etd_done(hcd, etd_num); - else - nonisoc_etd_done(hcd, etd_num); - } - - /* only enable SOF interrupt if it may be needed for the kludge */ - if (enable_sof_int) - set_register_bits(imx21, USBH_SYSIEN, USBH_SYSIEN_SOFINT); - else - clear_register_bits(imx21, USBH_SYSIEN, USBH_SYSIEN_SOFINT); - - - spin_unlock_irqrestore(&imx21->lock, flags); -} - -static irqreturn_t imx21_irq(struct usb_hcd *hcd) -{ - struct imx21 *imx21 = hcd_to_imx21(hcd); - u32 ints = readl(imx21->regs + USBH_SYSISR); - - if (ints & USBH_SYSIEN_HERRINT) - dev_dbg(imx21->dev, "Scheduling error\n"); - - if (ints & USBH_SYSIEN_SORINT) - dev_dbg(imx21->dev, "Scheduling overrun\n"); - - if (ints & (USBH_SYSISR_DONEINT | USBH_SYSISR_SOFINT)) - process_etds(hcd, imx21, ints & USBH_SYSISR_SOFINT); - - writel(ints, imx21->regs + USBH_SYSISR); - return IRQ_HANDLED; -} - -static void imx21_hc_endpoint_disable(struct usb_hcd *hcd, - struct usb_host_endpoint *ep) -{ - struct imx21 *imx21 = hcd_to_imx21(hcd); - unsigned long flags; - struct ep_priv *ep_priv; - int i; - - if (ep == NULL) - return; - - spin_lock_irqsave(&imx21->lock, flags); - ep_priv = ep->hcpriv; - dev_vdbg(imx21->dev, "disable ep=%p, ep->hcpriv=%p\n", ep, ep_priv); - - if (!list_empty(&ep->urb_list)) - dev_dbg(imx21->dev, "ep's URB list is not empty\n"); - - if (ep_priv != NULL) { - for (i = 0; i < NUM_ISO_ETDS; i++) { - if (ep_priv->etd[i] > -1) - dev_dbg(imx21->dev, "free etd %d for disable\n", - ep_priv->etd[i]); - - free_etd(imx21, ep_priv->etd[i]); - } - kfree(ep_priv); - ep->hcpriv = NULL; - } - - for (i = 0; i < USB_NUM_ETD; i++) { - if (imx21->etd[i].alloc && imx21->etd[i].ep == ep) { - dev_err(imx21->dev, - "Active etd %d for disabled ep=%p!\n", i, ep); - free_etd(imx21, i); - } - } - free_epdmem(imx21, ep); - spin_unlock_irqrestore(&imx21->lock, flags); -} - -/* =========================================== */ -/* Hub handling */ -/* =========================================== */ - -static int get_hub_descriptor(struct usb_hcd *hcd, - struct usb_hub_descriptor *desc) -{ - struct imx21 *imx21 = hcd_to_imx21(hcd); - desc->bDescriptorType = USB_DT_HUB; /* HUB descriptor */ - desc->bHubContrCurrent = 0; - - desc->bNbrPorts = readl(imx21->regs + USBH_ROOTHUBA) - & USBH_ROOTHUBA_NDNSTMPRT_MASK; - desc->bDescLength = 9; - desc->bPwrOn2PwrGood = 0; - desc->wHubCharacteristics = (__force __u16) cpu_to_le16( - HUB_CHAR_NO_LPSM | /* No power switching */ - HUB_CHAR_NO_OCPM); /* No over current protection */ - - desc->u.hs.DeviceRemovable[0] = 1 << 1; - desc->u.hs.DeviceRemovable[1] = ~0; - return 0; -} - -static int imx21_hc_hub_status_data(struct usb_hcd *hcd, char *buf) -{ - struct imx21 *imx21 = hcd_to_imx21(hcd); - int ports; - int changed = 0; - int i; - unsigned long flags; - - spin_lock_irqsave(&imx21->lock, flags); - ports = readl(imx21->regs + USBH_ROOTHUBA) - & USBH_ROOTHUBA_NDNSTMPRT_MASK; - if (ports > 7) { - ports = 7; - dev_err(imx21->dev, "ports %d > 7\n", ports); - } - for (i = 0; i < ports; i++) { - if (readl(imx21->regs + USBH_PORTSTAT(i)) & - (USBH_PORTSTAT_CONNECTSC | - USBH_PORTSTAT_PRTENBLSC | - USBH_PORTSTAT_PRTSTATSC | - USBH_PORTSTAT_OVRCURIC | - USBH_PORTSTAT_PRTRSTSC)) { - - changed = 1; - buf[0] |= 1 << (i + 1); - } - } - spin_unlock_irqrestore(&imx21->lock, flags); - - if (changed) - dev_info(imx21->dev, "Hub status changed\n"); - return changed; -} - -static int imx21_hc_hub_control(struct usb_hcd *hcd, - u16 typeReq, - u16 wValue, u16 wIndex, char *buf, u16 wLength) -{ - struct imx21 *imx21 = hcd_to_imx21(hcd); - int rc = 0; - u32 status_write = 0; - - switch (typeReq) { - case ClearHubFeature: - dev_dbg(imx21->dev, "ClearHubFeature\n"); - switch (wValue) { - case C_HUB_OVER_CURRENT: - dev_dbg(imx21->dev, " OVER_CURRENT\n"); - break; - case C_HUB_LOCAL_POWER: - dev_dbg(imx21->dev, " LOCAL_POWER\n"); - break; - default: - dev_dbg(imx21->dev, " unknown\n"); - rc = -EINVAL; - break; - } - break; - - case ClearPortFeature: - dev_dbg(imx21->dev, "ClearPortFeature\n"); - switch (wValue) { - case USB_PORT_FEAT_ENABLE: - dev_dbg(imx21->dev, " ENABLE\n"); - status_write = USBH_PORTSTAT_CURCONST; - break; - case USB_PORT_FEAT_SUSPEND: - dev_dbg(imx21->dev, " SUSPEND\n"); - status_write = USBH_PORTSTAT_PRTOVRCURI; - break; - case USB_PORT_FEAT_POWER: - dev_dbg(imx21->dev, " POWER\n"); - status_write = USBH_PORTSTAT_LSDEVCON; - break; - case USB_PORT_FEAT_C_ENABLE: - dev_dbg(imx21->dev, " C_ENABLE\n"); - status_write = USBH_PORTSTAT_PRTENBLSC; - break; - case USB_PORT_FEAT_C_SUSPEND: - dev_dbg(imx21->dev, " C_SUSPEND\n"); - status_write = USBH_PORTSTAT_PRTSTATSC; - break; - case USB_PORT_FEAT_C_CONNECTION: - dev_dbg(imx21->dev, " C_CONNECTION\n"); - status_write = USBH_PORTSTAT_CONNECTSC; - break; - case USB_PORT_FEAT_C_OVER_CURRENT: - dev_dbg(imx21->dev, " C_OVER_CURRENT\n"); - status_write = USBH_PORTSTAT_OVRCURIC; - break; - case USB_PORT_FEAT_C_RESET: - dev_dbg(imx21->dev, " C_RESET\n"); - status_write = USBH_PORTSTAT_PRTRSTSC; - break; - default: - dev_dbg(imx21->dev, " unknown\n"); - rc = -EINVAL; - break; - } - - break; - - case GetHubDescriptor: - dev_dbg(imx21->dev, "GetHubDescriptor\n"); - rc = get_hub_descriptor(hcd, (void *)buf); - break; - - case GetHubStatus: - dev_dbg(imx21->dev, " GetHubStatus\n"); - *(__le32 *) buf = 0; - break; - - case GetPortStatus: - dev_dbg(imx21->dev, "GetPortStatus: port: %d, 0x%x\n", - wIndex, USBH_PORTSTAT(wIndex - 1)); - *(__le32 *) buf = readl(imx21->regs + - USBH_PORTSTAT(wIndex - 1)); - break; - - case SetHubFeature: - dev_dbg(imx21->dev, "SetHubFeature\n"); - switch (wValue) { - case C_HUB_OVER_CURRENT: - dev_dbg(imx21->dev, " OVER_CURRENT\n"); - break; - - case C_HUB_LOCAL_POWER: - dev_dbg(imx21->dev, " LOCAL_POWER\n"); - break; - default: - dev_dbg(imx21->dev, " unknown\n"); - rc = -EINVAL; - break; - } - - break; - - case SetPortFeature: - dev_dbg(imx21->dev, "SetPortFeature\n"); - switch (wValue) { - case USB_PORT_FEAT_SUSPEND: - dev_dbg(imx21->dev, " SUSPEND\n"); - status_write = USBH_PORTSTAT_PRTSUSPST; - break; - case USB_PORT_FEAT_POWER: - dev_dbg(imx21->dev, " POWER\n"); - status_write = USBH_PORTSTAT_PRTPWRST; - break; - case USB_PORT_FEAT_RESET: - dev_dbg(imx21->dev, " RESET\n"); - status_write = USBH_PORTSTAT_PRTRSTST; - break; - default: - dev_dbg(imx21->dev, " unknown\n"); - rc = -EINVAL; - break; - } - break; - - default: - dev_dbg(imx21->dev, " unknown\n"); - rc = -EINVAL; - break; - } - - if (status_write) - writel(status_write, imx21->regs + USBH_PORTSTAT(wIndex - 1)); - return rc; -} - -/* =========================================== */ -/* Host controller management */ -/* =========================================== */ - -static int imx21_hc_reset(struct usb_hcd *hcd) -{ - struct imx21 *imx21 = hcd_to_imx21(hcd); - unsigned long timeout; - unsigned long flags; - - spin_lock_irqsave(&imx21->lock, flags); - - /* Reset the Host controller modules */ - writel(USBOTG_RST_RSTCTRL | USBOTG_RST_RSTRH | - USBOTG_RST_RSTHSIE | USBOTG_RST_RSTHC, - imx21->regs + USBOTG_RST_CTRL); - - /* Wait for reset to finish */ - timeout = jiffies + HZ; - while (readl(imx21->regs + USBOTG_RST_CTRL) != 0) { - if (time_after(jiffies, timeout)) { - spin_unlock_irqrestore(&imx21->lock, flags); - dev_err(imx21->dev, "timeout waiting for reset\n"); - return -ETIMEDOUT; - } - spin_unlock_irq(&imx21->lock); - schedule_timeout_uninterruptible(1); - spin_lock_irq(&imx21->lock); - } - spin_unlock_irqrestore(&imx21->lock, flags); - return 0; -} - -static int imx21_hc_start(struct usb_hcd *hcd) -{ - struct imx21 *imx21 = hcd_to_imx21(hcd); - unsigned long flags; - int i, j; - u32 hw_mode = USBOTG_HWMODE_CRECFG_HOST; - u32 usb_control = 0; - - hw_mode |= ((imx21->pdata->host_xcvr << USBOTG_HWMODE_HOSTXCVR_SHIFT) & - USBOTG_HWMODE_HOSTXCVR_MASK); - hw_mode |= ((imx21->pdata->otg_xcvr << USBOTG_HWMODE_OTGXCVR_SHIFT) & - USBOTG_HWMODE_OTGXCVR_MASK); - - if (imx21->pdata->host1_txenoe) - usb_control |= USBCTRL_HOST1_TXEN_OE; - - if (!imx21->pdata->host1_xcverless) - usb_control |= USBCTRL_HOST1_BYP_TLL; - - if (imx21->pdata->otg_ext_xcvr) - usb_control |= USBCTRL_OTC_RCV_RXDP; - - - spin_lock_irqsave(&imx21->lock, flags); - - writel((USBOTG_CLK_CTRL_HST | USBOTG_CLK_CTRL_MAIN), - imx21->regs + USBOTG_CLK_CTRL); - writel(hw_mode, imx21->regs + USBOTG_HWMODE); - writel(usb_control, imx21->regs + USBCTRL); - writel(USB_MISCCONTROL_SKPRTRY | USB_MISCCONTROL_ARBMODE, - imx21->regs + USB_MISCCONTROL); - - /* Clear the ETDs */ - for (i = 0; i < USB_NUM_ETD; i++) - for (j = 0; j < 4; j++) - etd_writel(imx21, i, j, 0); - - /* Take the HC out of reset */ - writel(USBH_HOST_CTRL_HCUSBSTE_OPERATIONAL | USBH_HOST_CTRL_CTLBLKSR_1, - imx21->regs + USBH_HOST_CTRL); - - /* Enable ports */ - if (imx21->pdata->enable_otg_host) - writel(USBH_PORTSTAT_PRTPWRST | USBH_PORTSTAT_PRTENABST, - imx21->regs + USBH_PORTSTAT(0)); - - if (imx21->pdata->enable_host1) - writel(USBH_PORTSTAT_PRTPWRST | USBH_PORTSTAT_PRTENABST, - imx21->regs + USBH_PORTSTAT(1)); - - if (imx21->pdata->enable_host2) - writel(USBH_PORTSTAT_PRTPWRST | USBH_PORTSTAT_PRTENABST, - imx21->regs + USBH_PORTSTAT(2)); - - - hcd->state = HC_STATE_RUNNING; - - /* Enable host controller interrupts */ - set_register_bits(imx21, USBH_SYSIEN, - USBH_SYSIEN_HERRINT | - USBH_SYSIEN_DONEINT | USBH_SYSIEN_SORINT); - set_register_bits(imx21, USBOTG_CINT_STEN, USBOTG_HCINT); - - spin_unlock_irqrestore(&imx21->lock, flags); - - return 0; -} - -static void imx21_hc_stop(struct usb_hcd *hcd) -{ - struct imx21 *imx21 = hcd_to_imx21(hcd); - unsigned long flags; - - spin_lock_irqsave(&imx21->lock, flags); - - writel(0, imx21->regs + USBH_SYSIEN); - clear_register_bits(imx21, USBOTG_CINT_STEN, USBOTG_HCINT); - clear_register_bits(imx21, USBOTG_CLK_CTRL_HST | USBOTG_CLK_CTRL_MAIN, - USBOTG_CLK_CTRL); - spin_unlock_irqrestore(&imx21->lock, flags); -} - -/* =========================================== */ -/* Driver glue */ -/* =========================================== */ - -static const struct hc_driver imx21_hc_driver = { - .description = hcd_name, - .product_desc = "IMX21 USB Host Controller", - .hcd_priv_size = sizeof(struct imx21), - - .flags = HCD_DMA | HCD_USB11, - .irq = imx21_irq, - - .reset = imx21_hc_reset, - .start = imx21_hc_start, - .stop = imx21_hc_stop, - - /* I/O requests */ - .urb_enqueue = imx21_hc_urb_enqueue, - .urb_dequeue = imx21_hc_urb_dequeue, - .endpoint_disable = imx21_hc_endpoint_disable, - - /* scheduling support */ - .get_frame_number = imx21_hc_get_frame, - - /* Root hub support */ - .hub_status_data = imx21_hc_hub_status_data, - .hub_control = imx21_hc_hub_control, - -}; - -static struct mx21_usbh_platform_data default_pdata = { - .host_xcvr = MX21_USBXCVR_TXDIF_RXDIF, - .otg_xcvr = MX21_USBXCVR_TXDIF_RXDIF, - .enable_host1 = 1, - .enable_host2 = 1, - .enable_otg_host = 1, - -}; - -static int imx21_remove(struct platform_device *pdev) -{ - struct usb_hcd *hcd = platform_get_drvdata(pdev); - struct imx21 *imx21 = hcd_to_imx21(hcd); - struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - - remove_debug_files(imx21); - usb_remove_hcd(hcd); - - if (res != NULL) { - clk_disable_unprepare(imx21->clk); - clk_put(imx21->clk); - iounmap(imx21->regs); - release_mem_region(res->start, resource_size(res)); - } - - kfree(hcd); - return 0; -} - - -static int imx21_probe(struct platform_device *pdev) -{ - struct usb_hcd *hcd; - struct imx21 *imx21; - struct resource *res; - int ret; - int irq; - - printk(KERN_INFO "%s\n", imx21_hc_driver.product_desc); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) - return -ENODEV; - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - - hcd = usb_create_hcd(&imx21_hc_driver, - &pdev->dev, dev_name(&pdev->dev)); - if (hcd == NULL) { - dev_err(&pdev->dev, "Cannot create hcd (%s)\n", - dev_name(&pdev->dev)); - return -ENOMEM; - } - - imx21 = hcd_to_imx21(hcd); - imx21->hcd = hcd; - imx21->dev = &pdev->dev; - imx21->pdata = dev_get_platdata(&pdev->dev); - if (!imx21->pdata) - imx21->pdata = &default_pdata; - - spin_lock_init(&imx21->lock); - INIT_LIST_HEAD(&imx21->dmem_list); - INIT_LIST_HEAD(&imx21->queue_for_etd); - INIT_LIST_HEAD(&imx21->queue_for_dmem); - create_debug_files(imx21); - - res = request_mem_region(res->start, resource_size(res), hcd_name); - if (!res) { - ret = -EBUSY; - goto failed_request_mem; - } - - imx21->regs = ioremap(res->start, resource_size(res)); - if (imx21->regs == NULL) { - dev_err(imx21->dev, "Cannot map registers\n"); - ret = -ENOMEM; - goto failed_ioremap; - } - - /* Enable clocks source */ - imx21->clk = clk_get(imx21->dev, NULL); - if (IS_ERR(imx21->clk)) { - dev_err(imx21->dev, "no clock found\n"); - ret = PTR_ERR(imx21->clk); - goto failed_clock_get; - } - - ret = clk_set_rate(imx21->clk, clk_round_rate(imx21->clk, 48000000)); - if (ret) - goto failed_clock_set; - ret = clk_prepare_enable(imx21->clk); - if (ret) - goto failed_clock_enable; - - dev_info(imx21->dev, "Hardware HC revision: 0x%02X\n", - (readl(imx21->regs + USBOTG_HWMODE) >> 16) & 0xFF); - - ret = usb_add_hcd(hcd, irq, 0); - if (ret != 0) { - dev_err(imx21->dev, "usb_add_hcd() returned %d\n", ret); - goto failed_add_hcd; - } - device_wakeup_enable(hcd->self.controller); - - return 0; - -failed_add_hcd: - clk_disable_unprepare(imx21->clk); -failed_clock_enable: -failed_clock_set: - clk_put(imx21->clk); -failed_clock_get: - iounmap(imx21->regs); -failed_ioremap: - release_mem_region(res->start, resource_size(res)); -failed_request_mem: - remove_debug_files(imx21); - usb_put_hcd(hcd); - return ret; -} - -static struct platform_driver imx21_hcd_driver = { - .driver = { - .name = hcd_name, - }, - .probe = imx21_probe, - .remove = imx21_remove, - .suspend = NULL, - .resume = NULL, -}; - -module_platform_driver(imx21_hcd_driver); - -MODULE_DESCRIPTION("i.MX21 USB Host controller"); -MODULE_AUTHOR("Martin Fuzzey"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:imx21-hcd"); diff --git a/drivers/usb/host/imx21-hcd.h b/drivers/usb/host/imx21-hcd.h deleted file mode 100644 index 96d16752a73e..000000000000 --- a/drivers/usb/host/imx21-hcd.h +++ /dev/null @@ -1,431 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * Macros and prototypes for i.MX21 - * - * Copyright (C) 2006 Loping Dog Embedded Systems - * Copyright (C) 2009 Martin Fuzzey - * Originally written by Jay Monkman - * Ported to 2.6.30, debugged and enhanced by Martin Fuzzey - */ - -#ifndef __LINUX_IMX21_HCD_H__ -#define __LINUX_IMX21_HCD_H__ - -#ifdef CONFIG_DYNAMIC_DEBUG -#define DEBUG -#endif - -#include - -#define NUM_ISO_ETDS 2 -#define USB_NUM_ETD 32 -#define DMEM_SIZE 4096 - -/* Register definitions */ -#define USBOTG_HWMODE 0x00 -#define USBOTG_HWMODE_ANASDBEN (1 << 14) -#define USBOTG_HWMODE_OTGXCVR_SHIFT 6 -#define USBOTG_HWMODE_OTGXCVR_MASK (3 << 6) -#define USBOTG_HWMODE_OTGXCVR_TD_RD (0 << 6) -#define USBOTG_HWMODE_OTGXCVR_TS_RD (2 << 6) -#define USBOTG_HWMODE_OTGXCVR_TD_RS (1 << 6) -#define USBOTG_HWMODE_OTGXCVR_TS_RS (3 << 6) -#define USBOTG_HWMODE_HOSTXCVR_SHIFT 4 -#define USBOTG_HWMODE_HOSTXCVR_MASK (3 << 4) -#define USBOTG_HWMODE_HOSTXCVR_TD_RD (0 << 4) -#define USBOTG_HWMODE_HOSTXCVR_TS_RD (2 << 4) -#define USBOTG_HWMODE_HOSTXCVR_TD_RS (1 << 4) -#define USBOTG_HWMODE_HOSTXCVR_TS_RS (3 << 4) -#define USBOTG_HWMODE_CRECFG_MASK (3 << 0) -#define USBOTG_HWMODE_CRECFG_HOST (1 << 0) -#define USBOTG_HWMODE_CRECFG_FUNC (2 << 0) -#define USBOTG_HWMODE_CRECFG_HNP (3 << 0) - -#define USBOTG_CINT_STAT 0x04 -#define USBOTG_CINT_STEN 0x08 -#define USBOTG_ASHNPINT (1 << 5) -#define USBOTG_ASFCINT (1 << 4) -#define USBOTG_ASHCINT (1 << 3) -#define USBOTG_SHNPINT (1 << 2) -#define USBOTG_FCINT (1 << 1) -#define USBOTG_HCINT (1 << 0) - -#define USBOTG_CLK_CTRL 0x0c -#define USBOTG_CLK_CTRL_FUNC (1 << 2) -#define USBOTG_CLK_CTRL_HST (1 << 1) -#define USBOTG_CLK_CTRL_MAIN (1 << 0) - -#define USBOTG_RST_CTRL 0x10 -#define USBOTG_RST_RSTI2C (1 << 15) -#define USBOTG_RST_RSTCTRL (1 << 5) -#define USBOTG_RST_RSTFC (1 << 4) -#define USBOTG_RST_RSTFSKE (1 << 3) -#define USBOTG_RST_RSTRH (1 << 2) -#define USBOTG_RST_RSTHSIE (1 << 1) -#define USBOTG_RST_RSTHC (1 << 0) - -#define USBOTG_FRM_INTVL 0x14 -#define USBOTG_FRM_REMAIN 0x18 -#define USBOTG_HNP_CSR 0x1c -#define USBOTG_HNP_ISR 0x2c -#define USBOTG_HNP_IEN 0x30 - -#define USBOTG_I2C_TXCVR_REG(x) (0x100 + (x)) -#define USBOTG_I2C_XCVR_DEVAD 0x118 -#define USBOTG_I2C_SEQ_OP_REG 0x119 -#define USBOTG_I2C_SEQ_RD_STARTAD 0x11a -#define USBOTG_I2C_OP_CTRL_REG 0x11b -#define USBOTG_I2C_SCLK_TO_SCK_HPER 0x11e -#define USBOTG_I2C_MASTER_INT_REG 0x11f - -#define USBH_HOST_CTRL 0x80 -#define USBH_HOST_CTRL_HCRESET (1 << 31) -#define USBH_HOST_CTRL_SCHDOVR(x) ((x) << 16) -#define USBH_HOST_CTRL_RMTWUEN (1 << 4) -#define USBH_HOST_CTRL_HCUSBSTE_RESET (0 << 2) -#define USBH_HOST_CTRL_HCUSBSTE_RESUME (1 << 2) -#define USBH_HOST_CTRL_HCUSBSTE_OPERATIONAL (2 << 2) -#define USBH_HOST_CTRL_HCUSBSTE_SUSPEND (3 << 2) -#define USBH_HOST_CTRL_CTLBLKSR_1 (0 << 0) -#define USBH_HOST_CTRL_CTLBLKSR_2 (1 << 0) -#define USBH_HOST_CTRL_CTLBLKSR_3 (2 << 0) -#define USBH_HOST_CTRL_CTLBLKSR_4 (3 << 0) - -#define USBH_SYSISR 0x88 -#define USBH_SYSISR_PSCINT (1 << 6) -#define USBH_SYSISR_FMOFINT (1 << 5) -#define USBH_SYSISR_HERRINT (1 << 4) -#define USBH_SYSISR_RESDETINT (1 << 3) -#define USBH_SYSISR_SOFINT (1 << 2) -#define USBH_SYSISR_DONEINT (1 << 1) -#define USBH_SYSISR_SORINT (1 << 0) - -#define USBH_SYSIEN 0x8c -#define USBH_SYSIEN_PSCINT (1 << 6) -#define USBH_SYSIEN_FMOFINT (1 << 5) -#define USBH_SYSIEN_HERRINT (1 << 4) -#define USBH_SYSIEN_RESDETINT (1 << 3) -#define USBH_SYSIEN_SOFINT (1 << 2) -#define USBH_SYSIEN_DONEINT (1 << 1) -#define USBH_SYSIEN_SORINT (1 << 0) - -#define USBH_XBUFSTAT 0x98 -#define USBH_YBUFSTAT 0x9c -#define USBH_XYINTEN 0xa0 -#define USBH_XFILLSTAT 0xa8 -#define USBH_YFILLSTAT 0xac -#define USBH_ETDENSET 0xc0 -#define USBH_ETDENCLR 0xc4 -#define USBH_IMMEDINT 0xcc -#define USBH_ETDDONESTAT 0xd0 -#define USBH_ETDDONEEN 0xd4 -#define USBH_FRMNUB 0xe0 -#define USBH_LSTHRESH 0xe4 - -#define USBH_ROOTHUBA 0xe8 -#define USBH_ROOTHUBA_PWRTOGOOD_MASK (0xff) -#define USBH_ROOTHUBA_PWRTOGOOD_SHIFT (24) -#define USBH_ROOTHUBA_NOOVRCURP (1 << 12) -#define USBH_ROOTHUBA_OVRCURPM (1 << 11) -#define USBH_ROOTHUBA_DEVTYPE (1 << 10) -#define USBH_ROOTHUBA_PWRSWTMD (1 << 9) -#define USBH_ROOTHUBA_NOPWRSWT (1 << 8) -#define USBH_ROOTHUBA_NDNSTMPRT_MASK (0xff) - -#define USBH_ROOTHUBB 0xec -#define USBH_ROOTHUBB_PRTPWRCM(x) (1 << ((x) + 16)) -#define USBH_ROOTHUBB_DEVREMOVE(x) (1 << (x)) - -#define USBH_ROOTSTAT 0xf0 -#define USBH_ROOTSTAT_CLRRMTWUE (1 << 31) -#define USBH_ROOTSTAT_OVRCURCHG (1 << 17) -#define USBH_ROOTSTAT_DEVCONWUE (1 << 15) -#define USBH_ROOTSTAT_OVRCURI (1 << 1) -#define USBH_ROOTSTAT_LOCPWRS (1 << 0) - -#define USBH_PORTSTAT(x) (0xf4 + ((x) * 4)) -#define USBH_PORTSTAT_PRTRSTSC (1 << 20) -#define USBH_PORTSTAT_OVRCURIC (1 << 19) -#define USBH_PORTSTAT_PRTSTATSC (1 << 18) -#define USBH_PORTSTAT_PRTENBLSC (1 << 17) -#define USBH_PORTSTAT_CONNECTSC (1 << 16) -#define USBH_PORTSTAT_LSDEVCON (1 << 9) -#define USBH_PORTSTAT_PRTPWRST (1 << 8) -#define USBH_PORTSTAT_PRTRSTST (1 << 4) -#define USBH_PORTSTAT_PRTOVRCURI (1 << 3) -#define USBH_PORTSTAT_PRTSUSPST (1 << 2) -#define USBH_PORTSTAT_PRTENABST (1 << 1) -#define USBH_PORTSTAT_CURCONST (1 << 0) - -#define USB_DMAREV 0x800 -#define USB_DMAINTSTAT 0x804 -#define USB_DMAINTSTAT_EPERR (1 << 1) -#define USB_DMAINTSTAT_ETDERR (1 << 0) - -#define USB_DMAINTEN 0x808 -#define USB_DMAINTEN_EPERRINTEN (1 << 1) -#define USB_DMAINTEN_ETDERRINTEN (1 << 0) - -#define USB_ETDDMAERSTAT 0x80c -#define USB_EPDMAERSTAT 0x810 -#define USB_ETDDMAEN 0x820 -#define USB_EPDMAEN 0x824 -#define USB_ETDDMAXTEN 0x828 -#define USB_EPDMAXTEN 0x82c -#define USB_ETDDMAENXYT 0x830 -#define USB_EPDMAENXYT 0x834 -#define USB_ETDDMABST4EN 0x838 -#define USB_EPDMABST4EN 0x83c - -#define USB_MISCCONTROL 0x840 -#define USB_MISCCONTROL_ISOPREVFRM (1 << 3) -#define USB_MISCCONTROL_SKPRTRY (1 << 2) -#define USB_MISCCONTROL_ARBMODE (1 << 1) -#define USB_MISCCONTROL_FILTCC (1 << 0) - -#define USB_ETDDMACHANLCLR 0x848 -#define USB_EPDMACHANLCLR 0x84c -#define USB_ETDSMSA(x) (0x900 + ((x) * 4)) -#define USB_EPSMSA(x) (0x980 + ((x) * 4)) -#define USB_ETDDMABUFPTR(x) (0xa00 + ((x) * 4)) -#define USB_EPDMABUFPTR(x) (0xa80 + ((x) * 4)) - -#define USB_ETD_DWORD(x, w) (0x200 + ((x) * 16) + ((w) * 4)) -#define DW0_ADDRESS 0 -#define DW0_ENDPNT 7 -#define DW0_DIRECT 11 -#define DW0_SPEED 13 -#define DW0_FORMAT 14 -#define DW0_MAXPKTSIZ 16 -#define DW0_HALTED 27 -#define DW0_TOGCRY 28 -#define DW0_SNDNAK 30 - -#define DW1_XBUFSRTAD 0 -#define DW1_YBUFSRTAD 16 - -#define DW2_RTRYDELAY 0 -#define DW2_POLINTERV 0 -#define DW2_STARTFRM 0 -#define DW2_RELPOLPOS 8 -#define DW2_DIRPID 16 -#define DW2_BUFROUND 18 -#define DW2_DELAYINT 19 -#define DW2_DATATOG 22 -#define DW2_ERRORCNT 24 -#define DW2_COMPCODE 28 - -#define DW3_TOTBYECNT 0 -#define DW3_PKTLEN0 0 -#define DW3_COMPCODE0 12 -#define DW3_PKTLEN1 16 -#define DW3_BUFSIZE 21 -#define DW3_COMPCODE1 28 - -#define USBCTRL 0x600 -#define USBCTRL_I2C_WU_INT_STAT (1 << 27) -#define USBCTRL_OTG_WU_INT_STAT (1 << 26) -#define USBCTRL_HOST_WU_INT_STAT (1 << 25) -#define USBCTRL_FNT_WU_INT_STAT (1 << 24) -#define USBCTRL_I2C_WU_INT_EN (1 << 19) -#define USBCTRL_OTG_WU_INT_EN (1 << 18) -#define USBCTRL_HOST_WU_INT_EN (1 << 17) -#define USBCTRL_FNT_WU_INT_EN (1 << 16) -#define USBCTRL_OTC_RCV_RXDP (1 << 13) -#define USBCTRL_HOST1_BYP_TLL (1 << 12) -#define USBCTRL_OTG_BYP_VAL(x) ((x) << 10) -#define USBCTRL_HOST1_BYP_VAL(x) ((x) << 8) -#define USBCTRL_OTG_PWR_MASK (1 << 6) -#define USBCTRL_HOST1_PWR_MASK (1 << 5) -#define USBCTRL_HOST2_PWR_MASK (1 << 4) -#define USBCTRL_USB_BYP (1 << 2) -#define USBCTRL_HOST1_TXEN_OE (1 << 1) - -#define USBOTG_DMEM 0x1000 - -/* Values in TD blocks */ -#define TD_DIR_SETUP 0 -#define TD_DIR_OUT 1 -#define TD_DIR_IN 2 -#define TD_FORMAT_CONTROL 0 -#define TD_FORMAT_ISO 1 -#define TD_FORMAT_BULK 2 -#define TD_FORMAT_INT 3 -#define TD_TOGGLE_CARRY 0 -#define TD_TOGGLE_DATA0 2 -#define TD_TOGGLE_DATA1 3 - -/* control transfer states */ -#define US_CTRL_SETUP 2 -#define US_CTRL_DATA 1 -#define US_CTRL_ACK 0 - -/* bulk transfer main state and 0-length packet */ -#define US_BULK 1 -#define US_BULK0 0 - -/*ETD format description*/ -#define IMX_FMT_CTRL 0x0 -#define IMX_FMT_ISO 0x1 -#define IMX_FMT_BULK 0x2 -#define IMX_FMT_INT 0x3 - -static char fmt_urb_to_etd[4] = { -/*PIPE_ISOCHRONOUS*/ IMX_FMT_ISO, -/*PIPE_INTERRUPT*/ IMX_FMT_INT, -/*PIPE_CONTROL*/ IMX_FMT_CTRL, -/*PIPE_BULK*/ IMX_FMT_BULK -}; - -/* condition (error) CC codes and mapping (OHCI like) */ - -#define TD_CC_NOERROR 0x00 -#define TD_CC_CRC 0x01 -#define TD_CC_BITSTUFFING 0x02 -#define TD_CC_DATATOGGLEM 0x03 -#define TD_CC_STALL 0x04 -#define TD_DEVNOTRESP 0x05 -#define TD_PIDCHECKFAIL 0x06 -/*#define TD_UNEXPECTEDPID 0x07 - reserved, not active on MX2*/ -#define TD_DATAOVERRUN 0x08 -#define TD_DATAUNDERRUN 0x09 -#define TD_BUFFEROVERRUN 0x0C -#define TD_BUFFERUNDERRUN 0x0D -#define TD_SCHEDULEOVERRUN 0x0E -#define TD_NOTACCESSED 0x0F - -static const int cc_to_error[16] = { - /* No Error */ 0, - /* CRC Error */ -EILSEQ, - /* Bit Stuff */ -EPROTO, - /* Data Togg */ -EILSEQ, - /* Stall */ -EPIPE, - /* DevNotResp */ -ETIMEDOUT, - /* PIDCheck */ -EPROTO, - /* UnExpPID */ -EPROTO, - /* DataOver */ -EOVERFLOW, - /* DataUnder */ -EREMOTEIO, - /* (for hw) */ -EIO, - /* (for hw) */ -EIO, - /* BufferOver */ -ECOMM, - /* BuffUnder */ -ENOSR, - /* (for HCD) */ -ENOSPC, - /* (for HCD) */ -EALREADY -}; - -/* HCD data associated with a usb core URB */ -struct urb_priv { - struct urb *urb; - struct usb_host_endpoint *ep; - int active; - int state; - struct td *isoc_td; - int isoc_remaining; - int isoc_status; -}; - -/* HCD data associated with a usb core endpoint */ -struct ep_priv { - struct usb_host_endpoint *ep; - struct list_head td_list; - struct list_head queue; - int etd[NUM_ISO_ETDS]; - int waiting_etd; -}; - -/* isoc packet */ -struct td { - struct list_head list; - struct urb *urb; - struct usb_host_endpoint *ep; - dma_addr_t dma_handle; - void *cpu_buffer; - int len; - int frame; - int isoc_index; -}; - -/* HCD data associated with a hardware ETD */ -struct etd_priv { - struct usb_host_endpoint *ep; - struct urb *urb; - struct td *td; - struct list_head queue; - dma_addr_t dma_handle; - void *cpu_buffer; - void *bounce_buffer; - int alloc; - int len; - int dmem_size; - int dmem_offset; - int active_count; -#ifdef DEBUG - int activated_frame; - int disactivated_frame; - int last_int_frame; - int last_req_frame; - u32 submitted_dwords[4]; -#endif -}; - -/* Hardware data memory info */ -struct imx21_dmem_area { - struct usb_host_endpoint *ep; - unsigned int offset; - unsigned int size; - struct list_head list; -}; - -#ifdef DEBUG -struct debug_usage_stats { - unsigned int value; - unsigned int maximum; -}; - -struct debug_stats { - unsigned long submitted; - unsigned long completed_ok; - unsigned long completed_failed; - unsigned long unlinked; - unsigned long queue_etd; - unsigned long queue_dmem; -}; - -struct debug_isoc_trace { - int schedule_frame; - int submit_frame; - int request_len; - int done_frame; - int done_len; - int cc; - struct td *td; -}; -#endif - -/* HCD data structure */ -struct imx21 { - spinlock_t lock; - struct device *dev; - struct usb_hcd *hcd; - struct mx21_usbh_platform_data *pdata; - struct list_head dmem_list; - struct list_head queue_for_etd; /* eps queued due to etd shortage */ - struct list_head queue_for_dmem; /* etds queued due to dmem shortage */ - struct etd_priv etd[USB_NUM_ETD]; - struct clk *clk; - void __iomem *regs; -#ifdef DEBUG - struct dentry *debug_root; - struct debug_stats nonisoc_stats; - struct debug_stats isoc_stats; - struct debug_usage_stats etd_usage; - struct debug_usage_stats dmem_usage; - struct debug_isoc_trace isoc_trace[20]; - struct debug_isoc_trace isoc_trace_failed[20]; - unsigned long debug_unblocks; - int isoc_trace_index; - int isoc_trace_index_failed; -#endif -}; - -#endif From e7018751d2e603381ae6028ba4dd21ec45ce38bb Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Fri, 13 Nov 2020 14:12:31 -0300 Subject: [PATCH 055/172] usb: host: ehci-mxc: Remove the driver The ehci-mxc driver was only used by i.MX non-DT platforms. Since 5.10-rc1, i.MX has been converted to a DT-only platform and all board files are gone. Remove the ehci-mxc driver as there are no more users at all. Acked-by: Alan Stern Signed-off-by: Fabio Estevam Link: https://lore.kernel.org/r/20201113171231.2205-1-festevam@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 7 - drivers/usb/host/Makefile | 1 - drivers/usb/host/ehci-mxc.c | 213 --------------------- include/linux/platform_data/usb-ehci-mxc.h | 14 -- 4 files changed, 235 deletions(-) delete mode 100644 drivers/usb/host/ehci-mxc.c delete mode 100644 include/linux/platform_data/usb-ehci-mxc.h diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index a633e9c15f7e..31e59309da1f 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -213,13 +213,6 @@ config USB_EHCI_FSL help Variation of ARC USB block used in some Freescale chips. -config USB_EHCI_MXC - tristate "Support for Freescale i.MX on-chip EHCI USB controller" - depends on ARCH_MXC || COMPILE_TEST - select USB_EHCI_ROOT_HUB_TT - help - Variation of ARC USB block used in some Freescale chips. - config USB_EHCI_HCD_NPCM7XX tristate "Support for Nuvoton NPCM7XX on-chip EHCI USB controller" depends on (USB_EHCI_HCD && ARCH_NPCM7XX) || COMPILE_TEST diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index d03f6f0764d2..c1b08703af10 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -40,7 +40,6 @@ obj-$(CONFIG_USB_PCI) += pci-quirks.o obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o obj-$(CONFIG_USB_EHCI_PCI) += ehci-pci.o obj-$(CONFIG_USB_EHCI_HCD_PLATFORM) += ehci-platform.o -obj-$(CONFIG_USB_EHCI_MXC) += ehci-mxc.o obj-$(CONFIG_USB_EHCI_HCD_NPCM7XX) += ehci-npcm7xx.o obj-$(CONFIG_USB_EHCI_HCD_OMAP) += ehci-omap.o obj-$(CONFIG_USB_EHCI_HCD_ORION) += ehci-orion.o diff --git a/drivers/usb/host/ehci-mxc.c b/drivers/usb/host/ehci-mxc.c deleted file mode 100644 index dc2676320527..000000000000 --- a/drivers/usb/host/ehci-mxc.c +++ /dev/null @@ -1,213 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (c) 2008 Sascha Hauer , Pengutronix - * Copyright (c) 2009 Daniel Mack - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "ehci.h" - -#define DRIVER_DESC "Freescale On-Chip EHCI Host driver" - -static const char hcd_name[] = "ehci-mxc"; - -#define ULPI_VIEWPORT_OFFSET 0x170 - -struct ehci_mxc_priv { - struct clk *usbclk, *ahbclk, *phyclk; -}; - -static struct hc_driver __read_mostly ehci_mxc_hc_driver; - -static const struct ehci_driver_overrides ehci_mxc_overrides __initconst = { - .extra_priv_size = sizeof(struct ehci_mxc_priv), -}; - -static int ehci_mxc_drv_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct mxc_usbh_platform_data *pdata = dev_get_platdata(dev); - struct usb_hcd *hcd; - struct resource *res; - int irq, ret; - struct ehci_mxc_priv *priv; - struct ehci_hcd *ehci; - - if (!pdata) { - dev_err(dev, "No platform data given, bailing out.\n"); - return -EINVAL; - } - - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - - hcd = usb_create_hcd(&ehci_mxc_hc_driver, dev, dev_name(dev)); - if (!hcd) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - hcd->regs = devm_ioremap_resource(dev, res); - if (IS_ERR(hcd->regs)) { - ret = PTR_ERR(hcd->regs); - goto err_alloc; - } - hcd->rsrc_start = res->start; - hcd->rsrc_len = resource_size(res); - - hcd->has_tt = 1; - ehci = hcd_to_ehci(hcd); - priv = (struct ehci_mxc_priv *) ehci->priv; - - /* enable clocks */ - priv->usbclk = devm_clk_get(dev, "ipg"); - if (IS_ERR(priv->usbclk)) { - ret = PTR_ERR(priv->usbclk); - goto err_alloc; - } - clk_prepare_enable(priv->usbclk); - - priv->ahbclk = devm_clk_get(dev, "ahb"); - if (IS_ERR(priv->ahbclk)) { - ret = PTR_ERR(priv->ahbclk); - goto err_clk_ahb; - } - clk_prepare_enable(priv->ahbclk); - - /* "dr" device has its own clock on i.MX51 */ - priv->phyclk = devm_clk_get(dev, "phy"); - if (IS_ERR(priv->phyclk)) - priv->phyclk = NULL; - if (priv->phyclk) - clk_prepare_enable(priv->phyclk); - - /* call platform specific init function */ - if (pdata->init) { - ret = pdata->init(pdev); - if (ret) { - dev_err(dev, "platform init failed\n"); - goto err_init; - } - /* platforms need some time to settle changed IO settings */ - mdelay(10); - } - - /* EHCI registers start at offset 0x100 */ - ehci->caps = hcd->regs + 0x100; - ehci->regs = hcd->regs + 0x100 + - HC_LENGTH(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase)); - - /* set up the PORTSCx register */ - ehci_writel(ehci, pdata->portsc, &ehci->regs->port_status[0]); - - /* is this really needed? */ - msleep(10); - - /* Initialize the transceiver */ - if (pdata->otg) { - pdata->otg->io_priv = hcd->regs + ULPI_VIEWPORT_OFFSET; - ret = usb_phy_init(pdata->otg); - if (ret) { - dev_err(dev, "unable to init transceiver, probably missing\n"); - ret = -ENODEV; - goto err_add; - } - ret = otg_set_vbus(pdata->otg->otg, 1); - if (ret) { - dev_err(dev, "unable to enable vbus on transceiver\n"); - goto err_add; - } - } - - platform_set_drvdata(pdev, hcd); - - ret = usb_add_hcd(hcd, irq, IRQF_SHARED); - if (ret) - goto err_add; - - device_wakeup_enable(hcd->self.controller); - return 0; - -err_add: - if (pdata && pdata->exit) - pdata->exit(pdev); -err_init: - if (priv->phyclk) - clk_disable_unprepare(priv->phyclk); - - clk_disable_unprepare(priv->ahbclk); -err_clk_ahb: - clk_disable_unprepare(priv->usbclk); -err_alloc: - usb_put_hcd(hcd); - return ret; -} - -static int ehci_mxc_drv_remove(struct platform_device *pdev) -{ - struct mxc_usbh_platform_data *pdata = dev_get_platdata(&pdev->dev); - struct usb_hcd *hcd = platform_get_drvdata(pdev); - struct ehci_hcd *ehci = hcd_to_ehci(hcd); - struct ehci_mxc_priv *priv = (struct ehci_mxc_priv *) ehci->priv; - - usb_remove_hcd(hcd); - - if (pdata && pdata->exit) - pdata->exit(pdev); - - if (pdata && pdata->otg) - usb_phy_shutdown(pdata->otg); - - clk_disable_unprepare(priv->usbclk); - clk_disable_unprepare(priv->ahbclk); - - if (priv->phyclk) - clk_disable_unprepare(priv->phyclk); - - usb_put_hcd(hcd); - return 0; -} - -MODULE_ALIAS("platform:mxc-ehci"); - -static struct platform_driver ehci_mxc_driver = { - .probe = ehci_mxc_drv_probe, - .remove = ehci_mxc_drv_remove, - .shutdown = usb_hcd_platform_shutdown, - .driver = { - .name = "mxc-ehci", - }, -}; - -static int __init ehci_mxc_init(void) -{ - if (usb_disabled()) - return -ENODEV; - - pr_info("%s: " DRIVER_DESC "\n", hcd_name); - - ehci_init_driver(&ehci_mxc_hc_driver, &ehci_mxc_overrides); - return platform_driver_register(&ehci_mxc_driver); -} -module_init(ehci_mxc_init); - -static void __exit ehci_mxc_cleanup(void) -{ - platform_driver_unregister(&ehci_mxc_driver); -} -module_exit(ehci_mxc_cleanup); - -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_AUTHOR("Sascha Hauer"); -MODULE_LICENSE("GPL"); diff --git a/include/linux/platform_data/usb-ehci-mxc.h b/include/linux/platform_data/usb-ehci-mxc.h deleted file mode 100644 index ad9794d09bc8..000000000000 --- a/include/linux/platform_data/usb-ehci-mxc.h +++ /dev/null @@ -1,14 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __INCLUDE_ASM_ARCH_MXC_EHCI_H -#define __INCLUDE_ASM_ARCH_MXC_EHCI_H - -struct mxc_usbh_platform_data { - int (*init)(struct platform_device *pdev); - int (*exit)(struct platform_device *pdev); - - unsigned int portsc; - struct usb_phy *otg; -}; - -#endif /* __INCLUDE_ASM_ARCH_MXC_EHCI_H */ - From 56c62080d5b57dac2c2cdd4a83571450e38ca763 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Fri, 13 Nov 2020 22:27:04 +0100 Subject: [PATCH 056/172] usb: hcd.h: Remove RUN_CONTEXT The last user of RUN_CONTEXT was removed in commit 97c17beb3b668 ("[PATCH] ehci-hcd (1/2): portability (2.4), tasklet,") in the history.git repo. There are no users of RUN_CONTEXT, remove it. Signed-off-by: Sebastian Andrzej Siewior Link: https://lore.kernel.org/r/20201113212704.2243807-1-bigeasy@linutronix.de Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/hcd.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 3dbb42c637c1..96281cd50ff6 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -734,10 +734,6 @@ static inline void usbmon_urb_complete(struct usb_bus *bus, struct urb *urb, /* random stuff */ -#define RUN_CONTEXT (in_irq() ? "in_irq" \ - : (in_interrupt() ? "in_interrupt" : "can sleep")) - - /* This rwsem is for use only by the hub driver and ehci-hcd. * Nobody else should touch it. */ From 13d40ff85da8a85935e3fd47061dee71a02af0d4 Mon Sep 17 00:00:00 2001 From: Utkarsh Patel Date: Fri, 13 Nov 2020 12:24:56 -0800 Subject: [PATCH 057/172] usb: typec: Correct the bit values for the Thunderbolt rounded/non-rounded cable support Rounded and non-rounded Thunderbolt cables are represented by two bits as per USB Type-C Connector specification v2.0 section F.2.6. Corrected that in the Thunderbolt 3 cable discover mode VDO. Signed-off-by: Utkarsh Patel Reviewed-by: Heikki Krogerus -- Changes in v2: - Removed the fixes tag as there is no functional implication. -- Link: https://lore.kernel.org/r/20201113202503.6559-2-utkarsh.h.patel@intel.com Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/typec_tbt.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/linux/usb/typec_tbt.h b/include/linux/usb/typec_tbt.h index 47c2d501ddce..aad648d14bb3 100644 --- a/include/linux/usb/typec_tbt.h +++ b/include/linux/usb/typec_tbt.h @@ -40,11 +40,16 @@ struct typec_thunderbolt_data { #define TBT_CABLE_USB3_PASSIVE 2 #define TBT_CABLE_10_AND_20GBPS 3 #define TBT_CABLE_ROUNDED BIT(19) +#define TBT_CABLE_ROUNDED_SUPPORT(_vdo_) \ + (((_vdo_) & GENMASK(20, 19)) >> 19) +#define TBT_GEN3_NON_ROUNDED 0 +#define TBT_GEN3_GEN4_ROUNDED_NON_ROUNDED 1 #define TBT_CABLE_OPTICAL BIT(21) #define TBT_CABLE_RETIMER BIT(22) #define TBT_CABLE_LINK_TRAINING BIT(23) #define TBT_SET_CABLE_SPEED(_s_) (((_s_) & GENMASK(2, 0)) << 16) +#define TBT_SET_CABLE_ROUNDED(_g_) (((_g_) & GENMASK(1, 0)) << 19) /* TBT3 Device Enter Mode VDO bits */ #define TBT_ENTER_MODE_CABLE_SPEED(s) TBT_SET_CABLE_SPEED(s) From 5384cffd7bce4652271c80ca081f5d39118e4923 Mon Sep 17 00:00:00 2001 From: Utkarsh Patel Date: Fri, 13 Nov 2020 12:24:57 -0800 Subject: [PATCH 058/172] platform/chrome: cros_ec_typec: Correct the Thunderbolt rounded/non-rounded cable support Thunderbolt rounded/non-rounded cable support is two bits value. Correcting it as per the Thunderbolt 3 cable discover mode VDO changes done in the Thunderbolt 3 alternate mode header. Signed-off-by: Utkarsh Patel Reviewed-by: Heikki Krogerus Acked-by: Enric Balletbo i Serra -- Changes in v2: - Removed the fixes tag as there is no functional implication. -- Link: https://lore.kernel.org/r/20201113202503.6559-3-utkarsh.h.patel@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/platform/chrome/cros_ec_typec.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 31be31161350..8111ed1fc574 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -438,8 +438,7 @@ static int cros_typec_enable_tbt(struct cros_typec_data *typec, if (pd_ctrl->control_flags & USB_PD_CTRL_ACTIVE_LINK_UNIDIR) data.cable_mode |= TBT_CABLE_LINK_TRAINING; - if (pd_ctrl->cable_gen) - data.cable_mode |= TBT_CABLE_ROUNDED; + data.cable_mode |= TBT_SET_CABLE_ROUNDED(pd_ctrl->cable_gen); /* Enter Mode VDO */ data.enter_vdo = TBT_SET_CABLE_SPEED(pd_ctrl->cable_speed); From c4f81392d5a6833a63ec5809e8bc5c2d0da565d8 Mon Sep 17 00:00:00 2001 From: Utkarsh Patel Date: Fri, 13 Nov 2020 12:24:58 -0800 Subject: [PATCH 059/172] usb: typec: intel_pmc_mux: Configure Thunderbolt cable generation bits Thunderbolt cable generation bits received as a part of Thunderbolt 3 cable discover mode VDO needs to be configured for Thunderbolt rounded and non-rounded cable support in the Thunderbolt alternate mode. Signed-off-by: Utkarsh Patel Reviewed-by: Heikki Krogerus -- Changes in v2: - No change. -- Link: https://lore.kernel.org/r/20201113202503.6559-4-utkarsh.h.patel@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux/intel_pmc_mux.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c index d7f63b74c6b1..aa3211f1c4c3 100644 --- a/drivers/usb/typec/mux/intel_pmc_mux.c +++ b/drivers/usb/typec/mux/intel_pmc_mux.c @@ -256,6 +256,7 @@ static int pmc_usb_mux_tbt(struct pmc_usb_port *port, struct typec_mux_state *state) { struct typec_thunderbolt_data *data = state->data; + u8 cable_rounded = TBT_CABLE_ROUNDED_SUPPORT(data->cable_mode); u8 cable_speed = TBT_CABLE_SPEED(data->cable_mode); struct altmode_req req = { }; @@ -284,6 +285,8 @@ pmc_usb_mux_tbt(struct pmc_usb_port *port, struct typec_mux_state *state) req.mode_data |= PMC_USB_ALTMODE_CABLE_SPD(cable_speed); + req.mode_data |= PMC_USB_ALTMODE_TBT_GEN(cable_rounded); + return pmc_usb_command(port, (void *)&req, sizeof(req)); } From 523a97aa3b756c1e0efe33ae48d450f8b0052073 Mon Sep 17 00:00:00 2001 From: Utkarsh Patel Date: Fri, 13 Nov 2020 12:24:59 -0800 Subject: [PATCH 060/172] usb: typec: Remove one bit support for the Thunderbolt rounded/non-rounded cable Two bits support for the Thunderbolt rounded/non-rounded cable has been added to the header file. Hence, removing unused TBT_CABLE_ROUNDED definition from the header file. Signed-off-by: Utkarsh Patel Reviewed-by: Heikki Krogerus -- changes in v2: - Removed the fixes tag as there is no functional implication. -- Link: https://lore.kernel.org/r/20201113202503.6559-5-utkarsh.h.patel@intel.com Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/typec_tbt.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/linux/usb/typec_tbt.h b/include/linux/usb/typec_tbt.h index aad648d14bb3..63dd44b72e0c 100644 --- a/include/linux/usb/typec_tbt.h +++ b/include/linux/usb/typec_tbt.h @@ -39,7 +39,6 @@ struct typec_thunderbolt_data { #define TBT_CABLE_USB3_GEN1 1 #define TBT_CABLE_USB3_PASSIVE 2 #define TBT_CABLE_10_AND_20GBPS 3 -#define TBT_CABLE_ROUNDED BIT(19) #define TBT_CABLE_ROUNDED_SUPPORT(_vdo_) \ (((_vdo_) & GENMASK(20, 19)) >> 19) #define TBT_GEN3_NON_ROUNDED 0 From 5a569343e8a618dc73edebe0957eb42f2ab476bd Mon Sep 17 00:00:00 2001 From: Yang Yingliang Date: Tue, 17 Nov 2020 14:15:00 +0800 Subject: [PATCH 061/172] usb/max3421: fix return error code in max3421_probe() retval may be reassigned to 0 after max3421_of_vbus_en_pin(), if allocate memory failed after this, max3421_probe() cann't return ENOMEM, fix this by moving assign retval afther max3421_probe(). Fixes: 721fdc83b31b ("usb: max3421: Add devicetree support") Reported-by: Hulk Robot Signed-off-by: Yang Yingliang Link: https://lore.kernel.org/r/20201117061500.3454223-1-yangyingliang@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/max3421-hcd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/host/max3421-hcd.c b/drivers/usb/host/max3421-hcd.c index 0894f6caccb2..ebb8180b52ab 100644 --- a/drivers/usb/host/max3421-hcd.c +++ b/drivers/usb/host/max3421-hcd.c @@ -1847,7 +1847,7 @@ max3421_probe(struct spi_device *spi) struct max3421_hcd *max3421_hcd; struct usb_hcd *hcd = NULL; struct max3421_hcd_platform_data *pdata = NULL; - int retval = -ENOMEM; + int retval; if (spi_setup(spi) < 0) { dev_err(&spi->dev, "Unable to setup SPI bus"); @@ -1889,6 +1889,7 @@ max3421_probe(struct spi_device *spi) goto error; } + retval = -ENOMEM; hcd = usb_create_hcd(&max3421_hcd_desc, &spi->dev, dev_name(&spi->dev)); if (!hcd) { From b0eec52fbe63fd8c3cffb8ef8d442c6fafb909b0 Mon Sep 17 00:00:00 2001 From: Lucas Tanure Date: Sun, 15 Nov 2020 10:28:37 +0000 Subject: [PATCH 062/172] USB: apple-mfi-fastcharge: Fix kfree after failed kzalloc kfree don't need to be called after a failed kzalloc Signed-off-by: Lucas Tanure Link: https://lore.kernel.org/r/20201115102837.331335-1-tanure@linux.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/apple-mfi-fastcharge.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/drivers/usb/misc/apple-mfi-fastcharge.c b/drivers/usb/misc/apple-mfi-fastcharge.c index 9de0171b5177..6dedd5498e8a 100644 --- a/drivers/usb/misc/apple-mfi-fastcharge.c +++ b/drivers/usb/misc/apple-mfi-fastcharge.c @@ -178,16 +178,13 @@ static int mfi_fc_probe(struct usb_device *udev) { struct power_supply_config battery_cfg = {}; struct mfi_device *mfi = NULL; - int err; if (!mfi_fc_match(udev)) return -ENODEV; mfi = kzalloc(sizeof(struct mfi_device), GFP_KERNEL); - if (!mfi) { - err = -ENOMEM; - goto error; - } + if (!mfi) + return -ENOMEM; battery_cfg.drv_data = mfi; @@ -197,18 +194,14 @@ static int mfi_fc_probe(struct usb_device *udev) &battery_cfg); if (IS_ERR(mfi->battery)) { dev_err(&udev->dev, "Can't register battery\n"); - err = PTR_ERR(mfi->battery); - goto error; + kfree(mfi); + return PTR_ERR(mfi->battery); } mfi->udev = usb_get_dev(udev); dev_set_drvdata(&udev->dev, mfi); return 0; - -error: - kfree(mfi); - return err; } static void mfi_fc_disconnect(struct usb_device *udev) From 8a5ca78f603977d6b9b2d7dcb3b1b6ef601d3cc7 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 16 Nov 2020 12:11:38 -0800 Subject: [PATCH 063/172] usb: pd: Add captive Type C cable type The USB Power Delivery Specification R3.0 adds a captive cable type to the "USB Type-C plug to USB Type-C/Captive" field (Bits 19-18, Passive/Active Cable VDO, Table 6-38 & 6-39). Add the corresponding definition to the Cable VDO header. Also add a helper macro to get the Type C cable connector type, when provided the cable VDO. Cc: Heikki Krogerus Signed-off-by: Prashant Malani Reviewed-by: Benson Leung Reviewed-by: Greg Kroah-Hartman Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20201116201150.2919178-2-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/pd_vdo.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/linux/usb/pd_vdo.h b/include/linux/usb/pd_vdo.h index 68bdc4e2f5a9..8c5cb5830754 100644 --- a/include/linux/usb/pd_vdo.h +++ b/include/linux/usb/pd_vdo.h @@ -177,7 +177,7 @@ * <31:28> :: Cable HW version * <27:24> :: Cable FW version * <23:20> :: Reserved, Shall be set to zero - * <19:18> :: type-C to Type-A/B/C (00b == A, 01 == B, 10 == C) + * <19:18> :: type-C to Type-A/B/C/Captive (00b == A, 01 == B, 10 == C, 11 == Captive) * <17> :: Type-C to Plug/Receptacle (0b == plug, 1b == receptacle) * <16:13> :: cable latency (0001 == <10ns(~1m length)) * <12:11> :: cable termination type (11b == both ends active VCONN req) @@ -193,6 +193,7 @@ #define CABLE_ATYPE 0 #define CABLE_BTYPE 1 #define CABLE_CTYPE 2 +#define CABLE_CAPTIVE 3 #define CABLE_PLUG 0 #define CABLE_RECEPTACLE 1 #define CABLE_CURR_1A5 0 @@ -208,6 +209,7 @@ | (tx1d) << 10 | (tx2d) << 9 | (rx1d) << 8 | (rx2d) << 7 \ | ((cur) & 0x3) << 5 | (vps) << 4 | (sopp) << 3 \ | ((usbss) & 0x7)) +#define VDO_TYPEC_CABLE_TYPE(vdo) (((vdo) >> 18) & 0x3) /* * AMA VDO From a0ccdc4a77a1b36b682ae60361879eca0a0f88d6 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 16 Nov 2020 12:11:40 -0800 Subject: [PATCH 064/172] usb: typec: Add number of altmodes partner attr Add a user-visible attribute for the number of alternate modes available in a partner. This allows userspace to determine whether there are any remaining alternate modes left to be registered by the kernel driver. It can begin executing any policy state machine after all available alternate modes have been registered with the connector class framework. This value is set to "-1" initially, signifying that a valid number of alternate modes haven't been set for the partner. Also add a sysfs file which exposes this attribute. The file remains hidden as long as the attribute value is -1. Cc: Benson Leung Cc: Heikki Krogerus Signed-off-by: Prashant Malani Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20201116201150.2919178-3-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-typec | 8 +++ drivers/usb/typec/class.c | 66 ++++++++++++++++++++- include/linux/usb/typec.h | 1 + 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec index b7794e02ad20..a195247d83ee 100644 --- a/Documentation/ABI/testing/sysfs-class-typec +++ b/Documentation/ABI/testing/sysfs-class-typec @@ -139,6 +139,14 @@ Description: Shows if the partner supports USB Power Delivery communication: Valid values: yes, no +What: /sys/class/typec/-partner/number_of_alternate_modes +Date: November 2020 +Contact: Prashant Malani +Description: + Shows the number of alternate modes which are advertised by the partner + during Power Delivery discovery. This file remains hidden until a value + greater than or equal to 0 is set by Type C port driver. + What: /sys/class/typec/-partner>/identity/ Date: April 2017 Contact: Heikki Krogerus diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 35eec707cb51..c7412ddbd311 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -33,6 +33,7 @@ struct typec_partner { struct usb_pd_identity *identity; enum typec_accessory accessory; struct ida mode_ids; + int num_altmodes; }; struct typec_port { @@ -532,12 +533,43 @@ static ssize_t supports_usb_power_delivery_show(struct device *dev, } static DEVICE_ATTR_RO(supports_usb_power_delivery); +static ssize_t number_of_alternate_modes_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_partner *p = to_typec_partner(dev); + + return sysfs_emit(buf, "%d\n", p->num_altmodes); +} +static DEVICE_ATTR_RO(number_of_alternate_modes); + static struct attribute *typec_partner_attrs[] = { &dev_attr_accessory_mode.attr, &dev_attr_supports_usb_power_delivery.attr, + &dev_attr_number_of_alternate_modes.attr, + NULL +}; + +static umode_t typec_partner_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct typec_partner *partner = to_typec_partner(kobj_to_dev(kobj)); + + if (attr == &dev_attr_number_of_alternate_modes.attr) { + if (partner->num_altmodes < 0) + return 0; + } + + return attr->mode; +} + +static struct attribute_group typec_partner_group = { + .is_visible = typec_partner_attr_is_visible, + .attrs = typec_partner_attrs +}; + +static const struct attribute_group *typec_partner_groups[] = { + &typec_partner_group, NULL }; -ATTRIBUTE_GROUPS(typec_partner); static void typec_partner_release(struct device *dev) { @@ -570,6 +602,37 @@ int typec_partner_set_identity(struct typec_partner *partner) } EXPORT_SYMBOL_GPL(typec_partner_set_identity); +/** + * typec_partner_set_num_altmodes - Set the number of available partner altmodes + * @partner: The partner to be updated. + * @num_alt_modes: The number of altmodes we want to specify as available. + * + * This routine is used to report the number of alternate modes supported by the + * partner. This value is *not* enforced in alternate mode registration routines. + * + * @partner.num_altmodes is set to -1 on partner registration, denoting that + * a valid value has not been set for it yet. + * + * Returns 0 on success or negative error number on failure. + */ +int typec_partner_set_num_altmodes(struct typec_partner *partner, int num_altmodes) +{ + int ret; + + if (num_altmodes < 0) + return -EINVAL; + + partner->num_altmodes = num_altmodes; + ret = sysfs_update_group(&partner->dev.kobj, &typec_partner_group); + if (ret < 0) + return ret; + + sysfs_notify(&partner->dev.kobj, NULL, "number_of_alternate_modes"); + + return 0; +} +EXPORT_SYMBOL_GPL(typec_partner_set_num_altmodes); + /** * typec_partner_register_altmode - Register USB Type-C Partner Alternate Mode * @partner: USB Type-C Partner that supports the alternate mode @@ -612,6 +675,7 @@ struct typec_partner *typec_register_partner(struct typec_port *port, ida_init(&partner->mode_ids); partner->usb_pd = desc->usb_pd; partner->accessory = desc->accessory; + partner->num_altmodes = -1; if (desc->identity) { /* diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index 6be558045942..bc6b1a71cb8a 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -126,6 +126,7 @@ struct typec_altmode_desc { enum typec_port_data roles; }; +int typec_partner_set_num_altmodes(struct typec_partner *partner, int num_altmodes); struct typec_altmode *typec_partner_register_altmode(struct typec_partner *partner, const struct typec_altmode_desc *desc); From 8115240c4fbd1c2705d1369f66168ec77fa2a7e4 Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Wed, 28 Oct 2020 23:31:31 -0700 Subject: [PATCH 065/172] usb: typec: tcpm: Refactor logic for new-source-frs-typec-current New source's current capability is now defined as string based device tree property through new-source-frs-typec-current. Refactor tcpm code to parse new-source-frs-typec-current and infer local port's new source current capability during frs. Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20201029063138.1429760-4-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index a6fae1f86505..a091a63758f4 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -363,8 +363,8 @@ struct tcpm_port { /* port belongs to a self powered device */ bool self_powered; - /* FRS */ - enum frs_typec_current frs_current; + /* Sink FRS */ + enum frs_typec_current new_source_frs_current; /* Sink caps have been queried */ bool sink_cap_done; @@ -1713,7 +1713,7 @@ static void tcpm_pd_data_request(struct tcpm_port *port, unsigned int cnt = pd_header_cnt_le(msg->header); unsigned int rev = pd_header_rev_le(msg->header); unsigned int i; - enum frs_typec_current frs_current; + enum frs_typec_current partner_frs_current; bool frs_enable; int ret; @@ -1786,12 +1786,13 @@ static void tcpm_pd_data_request(struct tcpm_port *port, for (i = 0; i < cnt; i++) port->sink_caps[i] = le32_to_cpu(msg->payload[i]); - frs_current = (port->sink_caps[0] & PDO_FIXED_FRS_CURR_MASK) >> + partner_frs_current = (port->sink_caps[0] & PDO_FIXED_FRS_CURR_MASK) >> PDO_FIXED_FRS_CURR_SHIFT; - frs_enable = frs_current && (frs_current <= port->frs_current); + frs_enable = partner_frs_current && (partner_frs_current <= + port->new_source_frs_current); tcpm_log(port, "Port partner FRS capable partner_frs_current:%u port_frs_current:%u enable:%c", - frs_current, port->frs_current, frs_enable ? 'y' : 'n'); + partner_frs_current, port->new_source_frs_current, frs_enable ? 'y' : 'n'); if (frs_enable) { ret = port->tcpc->enable_frs(port->tcpc, true); tcpm_log(port, "Enable FRS %s, ret:%d\n", ret ? "fail" : "success", ret); @@ -4808,9 +4809,10 @@ sink: /* FRS can only be supported byb DRP ports */ if (port->port_type == TYPEC_PORT_DRP) { - ret = fwnode_property_read_u32(fwnode, "frs-typec-current", &frs_current); + ret = fwnode_property_read_u32(fwnode, "new-source-frs-typec-current", + &frs_current); if (ret >= 0 && frs_current <= FRS_5V_3A) - port->frs_current = frs_current; + port->new_source_frs_current = frs_current; } return 0; From a30a00e37ceb094f949e4d96c2c586e6503b5d1d Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Wed, 28 Oct 2020 23:31:32 -0700 Subject: [PATCH 066/172] usb: typec: tcpm: frs sourcing vbus callback During FRS hardware autonomously starts to source vbus. Provide callback to perform chip specific operations. Signed-off-by: Badhri Jagan Sridharan Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20201029063138.1429760-5-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 9 +++++++++ include/linux/usb/tcpm.h | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index a091a63758f4..c196bd4b8a37 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -4091,7 +4091,16 @@ static void _tcpm_pd_vbus_on(struct tcpm_port *port) case SRC_TRY_DEBOUNCE: /* Do nothing, waiting for sink detection */ break; + case FR_SWAP_SEND: + case FR_SWAP_SEND_TIMEOUT: + case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF: + case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED: + if (port->tcpc->frs_sourcing_vbus) + port->tcpc->frs_sourcing_vbus(port->tcpc); + break; case FR_SWAP_SNK_SRC_NEW_SINK_READY: + if (port->tcpc->frs_sourcing_vbus) + port->tcpc->frs_sourcing_vbus(port->tcpc); tcpm_set_state(port, FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED, 0); break; diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index 09762d26fa0c..7303f518ba49 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -83,6 +83,9 @@ enum tcpm_transmit_type { * Optional; Called to enable/disable PD 3.0 fast role swap. * Enabling frs is accessory dependent as not all PD3.0 * accessories support fast role swap. + * @frs_sourcing_vbus: + * Optional; Called to notify that vbus is now being sourced. + * Low level drivers can perform chip specific operations, if any. */ struct tcpc_dev { struct fwnode_handle *fwnode; @@ -109,6 +112,7 @@ struct tcpc_dev { const struct pd_message *msg); int (*set_bist_data)(struct tcpc_dev *dev, bool on); int (*enable_frs)(struct tcpc_dev *dev, bool enable); + void (*frs_sourcing_vbus)(struct tcpc_dev *dev); }; struct tcpm_port; From a57d253fc0582d223464705a5103cfb1930a06cd Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Wed, 28 Oct 2020 23:31:33 -0700 Subject: [PATCH 067/172] usb: typec: tcpci: frs sourcing vbus callback During FRS hardware autonomously starts to source vbus. Provide callback to perform chip specific operations. Signed-off-by: Badhri Jagan Sridharan Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20201029063138.1429760-6-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci.c | 9 +++++++++ drivers/usb/typec/tcpm/tcpci.h | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index f9f0af64da5f..f91688e43991 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -284,6 +284,14 @@ static int tcpci_enable_frs(struct tcpc_dev *dev, bool enable) return ret; } +static void tcpci_frs_sourcing_vbus(struct tcpc_dev *dev) +{ + struct tcpci *tcpci = tcpc_to_tcpci(dev); + + if (tcpci->data->frs_sourcing_vbus) + tcpci->data->frs_sourcing_vbus(tcpci, tcpci->data); +} + static int tcpci_set_bist_data(struct tcpc_dev *tcpc, bool enable) { struct tcpci *tcpci = tcpc_to_tcpci(tcpc); @@ -628,6 +636,7 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) tcpci->tcpc.pd_transmit = tcpci_pd_transmit; tcpci->tcpc.set_bist_data = tcpci_set_bist_data; tcpci->tcpc.enable_frs = tcpci_enable_frs; + tcpci->tcpc.frs_sourcing_vbus = tcpci_frs_sourcing_vbus; err = tcpci_parse_config(tcpci); if (err < 0) diff --git a/drivers/usb/typec/tcpm/tcpci.h b/drivers/usb/typec/tcpm/tcpci.h index 5ef07a56d67a..b418fe11b527 100644 --- a/drivers/usb/typec/tcpm/tcpci.h +++ b/drivers/usb/typec/tcpm/tcpci.h @@ -143,6 +143,9 @@ /* * @TX_BUF_BYTE_x_hidden * optional; Set when TX_BUF_BYTE_x can only be accessed through I2C_WRITE_BYTE_COUNT. + * @frs_sourcing_vbus: + * Optional; Callback to perform chip specific operations when FRS + * is sourcing vbus. */ struct tcpci; struct tcpci_data { @@ -154,6 +157,7 @@ struct tcpci_data { int (*start_drp_toggling)(struct tcpci *tcpci, struct tcpci_data *data, enum typec_cc_status cc); int (*set_vbus)(struct tcpci *tcpci, struct tcpci_data *data, bool source, bool sink); + void (*frs_sourcing_vbus)(struct tcpci *tcpci, struct tcpci_data *data); }; struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data); From 2fc58b36e919902ae2f17531783d8debadcc2a27 Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Wed, 28 Oct 2020 23:31:34 -0700 Subject: [PATCH 068/172] usb: typec: tcpci_maxim: Fix vbus stuck on upon diconnecting sink Occasionally, POWER_STATUS.sourcing_vbus takes a while to clear after writing to MAX_BUCK_BOOST_OP register. This causes vbus to turn back on while disconnecting the sink. Overcome this issue by writing into MAX_BUCK_BOOST_OP during frs while sourcing vbu, instead of always into the register whenever POWER_STATUS.sourcing_vbus is set. Signed-off-by: Badhri Jagan Sridharan Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20201029063138.1429760-7-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci_maxim.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.c b/drivers/usb/typec/tcpm/tcpci_maxim.c index 723d7dd38f75..43dcad95e897 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim.c @@ -238,23 +238,22 @@ static void process_power_status(struct max_tcpci_chip *chip) if (ret < 0) return; - if (pwr_status == 0xff) { + if (pwr_status == 0xff) max_tcpci_init_regs(chip); - } else if (pwr_status & TCPC_POWER_STATUS_SOURCING_VBUS) { + else if (pwr_status & TCPC_POWER_STATUS_SOURCING_VBUS) tcpm_sourcing_vbus(chip->port); - /* - * Alawys re-enable boost here. - * In normal case, when say an headset is attached, TCPM would - * have instructed to TCPC to enable boost, so the call is a - * no-op. - * But for Fast Role Swap case, Boost turns on autonomously without - * AP intervention, but, needs AP to enable source mode explicitly - * for AP to regain control. - */ - max_tcpci_set_vbus(chip->tcpci, &chip->data, true, false); - } else { + else tcpm_vbus_change(chip->port); - } +} + +static void max_tcpci_frs_sourcing_vbus(struct tcpci *tcpci, struct tcpci_data *tdata) +{ + /* + * For Fast Role Swap case, Boost turns on autonomously without + * AP intervention, but, needs AP to enable source mode explicitly + * for AP to regain control. + */ + max_tcpci_set_vbus(tcpci, tdata, true, false); } static void process_tx(struct max_tcpci_chip *chip, u16 status) @@ -441,6 +440,7 @@ static int max_tcpci_probe(struct i2c_client *client, const struct i2c_device_id chip->data.start_drp_toggling = max_tcpci_start_toggling; chip->data.TX_BUF_BYTE_x_hidden = true; chip->data.init = tcpci_init; + chip->data.frs_sourcing_vbus = max_tcpci_frs_sourcing_vbus; max_tcpci_init_regs(chip); chip->tcpci = tcpci_register_port(chip->dev, &chip->data); From f321a02caebdd0c56e167610cda2fa148cd96e8b Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Wed, 28 Oct 2020 23:31:35 -0700 Subject: [PATCH 069/172] usb: typec: tcpm: Implement enabling Auto Discharge disconnect support TCPCI spec allows TCPC hardware to autonomously discharge the vbus capacitance upon disconnect. The expectation is that the TCPM enables AutoDischargeDisconnect while entering SNK/SRC_ATTACHED states. Hardware then automously discharges vbus when the vbus falls below a certain threshold i.e. VBUS_SINK_DISCONNECT_THRESHOLD. Apart from enabling the vbus discharge circuit, AutoDischargeDisconnect is also used a flag to move TCPCI based TCPC implementations into Attached.Snk/Attached.Src state as mentioned in Figure 4-15. TCPC State Diagram before a Connection of the USB Type-C Port Controller Interface Specification. In such TCPC implementations, setting AutoDischargeDisconnect would prevent TCPC into entering "Connection_Invalid" state as well. Signed-off-by: Badhri Jagan Sridharan Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20201029063138.1429760-8-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 60 ++++++++++++++++++++++++++++++++--- include/linux/usb/tcpm.h | 15 +++++++++ 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index c196bd4b8a37..4aac0efdb720 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -1706,6 +1706,24 @@ static void tcpm_handle_alert(struct tcpm_port *port, const __le32 *payload, } } +static int tcpm_set_auto_vbus_discharge_threshold(struct tcpm_port *port, + enum typec_pwr_opmode mode, bool pps_active, + u32 requested_vbus_voltage) +{ + int ret; + + if (!port->tcpc->set_auto_vbus_discharge_threshold) + return 0; + + ret = port->tcpc->set_auto_vbus_discharge_threshold(port->tcpc, mode, pps_active, + requested_vbus_voltage); + tcpm_log_force(port, + "set_auto_vbus_discharge_threshold mode:%d pps_active:%c vbus:%u ret:%d", + mode, pps_active ? 'y' : 'n', requested_vbus_voltage, ret); + + return ret; +} + static void tcpm_pd_data_request(struct tcpm_port *port, const struct pd_message *msg) { @@ -1876,6 +1894,10 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, port->current_limit, port->supply_voltage); port->explicit_contract = true; + tcpm_set_auto_vbus_discharge_threshold(port, + TYPEC_PWR_MODE_PD, + port->pps_data.active, + port->supply_voltage); tcpm_set_state(port, SNK_READY, 0); } else { /* @@ -2790,8 +2812,12 @@ static int tcpm_src_attach(struct tcpm_port *port) if (ret < 0) return ret; - ret = tcpm_set_roles(port, true, TYPEC_SOURCE, - tcpm_data_role_for_source(port)); + if (port->tcpc->enable_auto_vbus_discharge) { + ret = port->tcpc->enable_auto_vbus_discharge(port->tcpc, true); + tcpm_log_force(port, "enable vbus discharge ret:%d", ret); + } + + ret = tcpm_set_roles(port, true, TYPEC_SOURCE, tcpm_data_role_for_source(port)); if (ret < 0) return ret; @@ -2858,6 +2884,12 @@ static void tcpm_unregister_altmodes(struct tcpm_port *port) static void tcpm_reset_port(struct tcpm_port *port) { + int ret; + + if (port->tcpc->enable_auto_vbus_discharge) { + ret = port->tcpc->enable_auto_vbus_discharge(port->tcpc, false); + tcpm_log_force(port, "Disable vbus discharge ret:%d", ret); + } tcpm_unregister_altmodes(port); tcpm_typec_disconnect(port); port->attached = false; @@ -2922,8 +2954,13 @@ static int tcpm_snk_attach(struct tcpm_port *port) if (ret < 0) return ret; - ret = tcpm_set_roles(port, true, TYPEC_SINK, - tcpm_data_role_for_sink(port)); + if (port->tcpc->enable_auto_vbus_discharge) { + tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, VSAFE5V); + ret = port->tcpc->enable_auto_vbus_discharge(port->tcpc, true); + tcpm_log_force(port, "enable vbus discharge ret:%d", ret); + } + + ret = tcpm_set_roles(port, true, TYPEC_SINK, tcpm_data_role_for_sink(port)); if (ret < 0) return ret; @@ -3507,6 +3544,8 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, SRC_UNATTACHED, PD_T_PS_SOURCE_ON); break; case SNK_HARD_RESET_SINK_OFF: + /* Do not discharge/disconnect during hard reseet */ + tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, 0); memset(&port->pps_data, 0, sizeof(port->pps_data)); tcpm_set_vconn(port, false); if (port->pd_capable) @@ -3549,6 +3588,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_charge(port, true); } tcpm_set_attached_state(port, true); + tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, VSAFE5V); tcpm_set_state(port, SNK_STARTUP, 0); break; @@ -3650,6 +3690,10 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, PR_SWAP_SNK_SRC_SINK_OFF, 0); break; case PR_SWAP_SRC_SNK_TRANSITION_OFF: + /* + * Prevent vbus discharge circuit from turning on during PR_SWAP + * as this is not a disconnect. + */ tcpm_set_vbus(port, false); port->explicit_contract = false; /* allow time for Vbus discharge, must be < tSrcSwapStdby */ @@ -3678,9 +3722,17 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state_cond(port, SNK_UNATTACHED, PD_T_PS_SOURCE_ON); break; case PR_SWAP_SRC_SNK_SINK_ON: + /* Set the vbus disconnect threshold for implicit contract */ + tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, VSAFE5V); tcpm_set_state(port, SNK_STARTUP, 0); break; case PR_SWAP_SNK_SRC_SINK_OFF: + /* + * Prevent vbus discharge circuit from turning on during PR_SWAP + * as this is not a disconnect. + */ + tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, + port->pps_data.active, 0); tcpm_set_charge(port, false); tcpm_set_state(port, hard_reset_state(port), PD_T_PS_SOURCE_OFF); diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index 7303f518ba49..e68aaa12886f 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -86,6 +86,18 @@ enum tcpm_transmit_type { * @frs_sourcing_vbus: * Optional; Called to notify that vbus is now being sourced. * Low level drivers can perform chip specific operations, if any. + * @enable_auto_vbus_discharge: + * Optional; TCPCI spec based TCPC implementations can optionally + * support hardware to autonomously dischrge vbus upon disconnecting + * as sink or source. TCPM signals TCPC to enable the mechanism upon + * entering connected state and signals disabling upon disconnect. + * @set_auto_vbus_discharge_threshold: + * Mandatory when enable_auto_vbus_discharge is implemented. TCPM + * calls this function to allow lower levels drivers to program the + * vbus threshold voltage below which the vbus discharge circuit + * will be turned on. requested_vbus_voltage is set to 0 when vbus + * is going to disappear knowingly i.e. during PR_SWAP and + * HARD_RESET etc. */ struct tcpc_dev { struct fwnode_handle *fwnode; @@ -113,6 +125,9 @@ struct tcpc_dev { int (*set_bist_data)(struct tcpc_dev *dev, bool on); int (*enable_frs)(struct tcpc_dev *dev, bool enable); void (*frs_sourcing_vbus)(struct tcpc_dev *dev); + int (*enable_auto_vbus_discharge)(struct tcpc_dev *dev, bool enable); + int (*set_auto_vbus_discharge_threshold)(struct tcpc_dev *dev, enum typec_pwr_opmode mode, + bool pps_active, u32 requested_vbus_voltage); }; struct tcpm_port; From e1a97bf80a022cdd7a5746a7de8e19f02203d112 Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Wed, 28 Oct 2020 23:31:36 -0700 Subject: [PATCH 070/172] usb: typec: tcpci: Implement Auto discharge disconnect callbacks vImplement callbacks for enabling/disabling POWER_CONTROL.AutoDischargeDisconnect. Programs VBUS_SINK_DISCONNECT_THRESHOLD based on the voltage requested as sink, mode of operation. The programmed threshold is based on vSinkDisconnect and vSinkDisconnectPD values. Add auto_discharge_disconnect to tdata to allow TCPC chip level drivers enable AutoDischargeDisconnect. Signed-off-by: Badhri Jagan Sridharan Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20201029063138.1429760-9-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci.c | 63 +++++++++++++++++++++++++++++++++- drivers/usb/typec/tcpm/tcpci.h | 14 ++++++-- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index f91688e43991..12d983a75510 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -18,7 +18,10 @@ #include "tcpci.h" -#define PD_RETRY_COUNT 3 +#define PD_RETRY_COUNT 3 +#define AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV 3500 +#define AUTO_DISCHARGE_PD_HEADROOM_MV 850 +#define AUTO_DISCHARGE_PPS_HEADROOM_MV 1250 struct tcpci { struct device *dev; @@ -268,6 +271,58 @@ static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable) enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0); } +static int tcpci_enable_auto_vbus_discharge(struct tcpc_dev *dev, bool enable) +{ + struct tcpci *tcpci = tcpc_to_tcpci(dev); + int ret; + + ret = regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, TCPC_POWER_CTRL_AUTO_DISCHARGE, + enable ? TCPC_POWER_CTRL_AUTO_DISCHARGE : 0); + return ret; +} + +static int tcpci_set_auto_vbus_discharge_threshold(struct tcpc_dev *dev, enum typec_pwr_opmode mode, + bool pps_active, u32 requested_vbus_voltage_mv) +{ + struct tcpci *tcpci = tcpc_to_tcpci(dev); + unsigned int pwr_ctrl, threshold = 0; + int ret; + + /* + * Indicates that vbus is going to go away due PR_SWAP, hard reset etc. + * Do not discharge vbus here. + */ + if (requested_vbus_voltage_mv == 0) + goto write_thresh; + + ret = regmap_read(tcpci->regmap, TCPC_POWER_CTRL, &pwr_ctrl); + if (ret < 0) + return ret; + + if (pwr_ctrl & TCPC_FAST_ROLE_SWAP_EN) { + /* To prevent disconnect when the source is fast role swap is capable. */ + threshold = AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV; + } else if (mode == TYPEC_PWR_MODE_PD) { + if (pps_active) + threshold = (95 * requested_vbus_voltage_mv / 100) - + AUTO_DISCHARGE_PD_HEADROOM_MV; + else + threshold = (95 * requested_vbus_voltage_mv / 100) - + AUTO_DISCHARGE_PPS_HEADROOM_MV; + } else { + /* 3.5V for non-pd sink */ + threshold = AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV; + } + + threshold = threshold / TCPC_VBUS_SINK_DISCONNECT_THRESH_LSB_MV; + + if (threshold > TCPC_VBUS_SINK_DISCONNECT_THRESH_MAX) + return -EINVAL; + +write_thresh: + return tcpci_write16(tcpci, TCPC_VBUS_SINK_DISCONNECT_THRESH, threshold); +} + static int tcpci_enable_frs(struct tcpc_dev *dev, bool enable) { struct tcpci *tcpci = tcpc_to_tcpci(dev); @@ -638,6 +693,12 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) tcpci->tcpc.enable_frs = tcpci_enable_frs; tcpci->tcpc.frs_sourcing_vbus = tcpci_frs_sourcing_vbus; + if (tcpci->data->auto_discharge_disconnect) { + tcpci->tcpc.enable_auto_vbus_discharge = tcpci_enable_auto_vbus_discharge; + tcpci->tcpc.set_auto_vbus_discharge_threshold = + tcpci_set_auto_vbus_discharge_threshold; + } + err = tcpci_parse_config(tcpci); if (err < 0) return ERR_PTR(err); diff --git a/drivers/usb/typec/tcpm/tcpci.h b/drivers/usb/typec/tcpm/tcpci.h index b418fe11b527..3fe313655f0c 100644 --- a/drivers/usb/typec/tcpm/tcpci.h +++ b/drivers/usb/typec/tcpm/tcpci.h @@ -8,6 +8,8 @@ #ifndef __LINUX_USB_TCPCI_H #define __LINUX_USB_TCPCI_H +#include + #define TCPC_VENDOR_ID 0x0 #define TCPC_PRODUCT_ID 0x2 #define TCPC_BCD_DEV 0x4 @@ -67,6 +69,7 @@ #define TCPC_POWER_CTRL 0x1c #define TCPC_POWER_CTRL_VCONN_ENABLE BIT(0) +#define TCPC_POWER_CTRL_AUTO_DISCHARGE BIT(4) #define TCPC_FAST_ROLE_SWAP_EN BIT(7) #define TCPC_CC_STATUS 0x1d @@ -133,6 +136,8 @@ #define TCPC_VBUS_VOLTAGE 0x70 #define TCPC_VBUS_SINK_DISCONNECT_THRESH 0x72 +#define TCPC_VBUS_SINK_DISCONNECT_THRESH_LSB_MV 25 +#define TCPC_VBUS_SINK_DISCONNECT_THRESH_MAX 0x3ff #define TCPC_VBUS_STOP_DISCHARGE_THRESH 0x74 #define TCPC_VBUS_VOLTAGE_ALARM_HI_CFG 0x76 #define TCPC_VBUS_VOLTAGE_ALARM_LO_CFG 0x78 @@ -140,17 +145,22 @@ /* I2C_WRITE_BYTE_COUNT + 1 when TX_BUF_BYTE_x is only accessible I2C_WRITE_BYTE_COUNT */ #define TCPC_TRANSMIT_BUFFER_MAX_LEN 31 +struct tcpci; + /* - * @TX_BUF_BYTE_x_hidden + * @TX_BUF_BYTE_x_hidden: * optional; Set when TX_BUF_BYTE_x can only be accessed through I2C_WRITE_BYTE_COUNT. * @frs_sourcing_vbus: * Optional; Callback to perform chip specific operations when FRS * is sourcing vbus. + * @auto_discharge_disconnect: + * Optional; Enables TCPC to autonously discharge vbus on disconnect. */ -struct tcpci; struct tcpci_data { struct regmap *regmap; unsigned char TX_BUF_BYTE_x_hidden:1; + unsigned char auto_discharge_disconnect:1; + int (*init)(struct tcpci *tcpci, struct tcpci_data *data); int (*set_vconn)(struct tcpci *tcpci, struct tcpci_data *data, bool enable); From cccaee0e0aadb36edf38298d400ed76e3de21dd5 Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Wed, 28 Oct 2020 23:31:37 -0700 Subject: [PATCH 071/172] usb: typec: tcpci_maxim: Enable auto discharge disconnect Enable auto discharge disconnect for Maxim TCPC. Signed-off-by: Badhri Jagan Sridharan Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20201029063138.1429760-10-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci_maxim.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.c b/drivers/usb/typec/tcpm/tcpci_maxim.c index 43dcad95e897..55dea33387ec 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim.c @@ -441,6 +441,7 @@ static int max_tcpci_probe(struct i2c_client *client, const struct i2c_device_id chip->data.TX_BUF_BYTE_x_hidden = true; chip->data.init = tcpci_init; chip->data.frs_sourcing_vbus = max_tcpci_frs_sourcing_vbus; + chip->data.auto_discharge_disconnect = true; max_tcpci_init_regs(chip); chip->tcpci = tcpci_register_port(chip->dev, &chip->data); From 7695cae24b29edd2dbd3b3a77a7264cd6d9ca67a Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Wed, 28 Oct 2020 23:31:38 -0700 Subject: [PATCH 072/172] usb: typec: tcpci_maxim: Fix uninitialized return variable New smatch warnings: drivers/usb/typec/tcpm/tcpci_maxim.c:324 max_tcpci_irq() error: uninitialized symbol 'irq_return'. drivers/usb/typec/tcpm/tcpci_maxim.c:407 max_tcpci_probe() warn: passing zero to 'PTR_ERR' The change fixes the above warnings by initializing irq_return and replacing IS_ERR_OR_NULL with IS_ERR. Reported-by: kernel test robot Reported-by: Dan Carpenter Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20201029063138.1429760-11-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci_maxim.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.c b/drivers/usb/typec/tcpm/tcpci_maxim.c index 55dea33387ec..3b3d36f94709 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim.c @@ -343,7 +343,7 @@ static irqreturn_t max_tcpci_irq(int irq, void *dev_id) { struct max_tcpci_chip *chip = dev_id; u16 status; - irqreturn_t irq_return; + irqreturn_t irq_return = IRQ_HANDLED; int ret; if (!chip->port) @@ -445,7 +445,7 @@ static int max_tcpci_probe(struct i2c_client *client, const struct i2c_device_id max_tcpci_init_regs(chip); chip->tcpci = tcpci_register_port(chip->dev, &chip->data); - if (IS_ERR_OR_NULL(chip->tcpci)) { + if (IS_ERR(chip->tcpci)) { dev_err(&client->dev, "TCPCI port registration failed"); ret = PTR_ERR(chip->tcpci); return PTR_ERR(chip->tcpci); From 6393734ebb8c128edabcc1d357c8344f8c38d80f Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Wed, 28 Oct 2020 23:31:29 -0700 Subject: [PATCH 073/172] dt-bindings: usb: Maxim type-c controller device tree binding document Add device tree binding document for Maxim 33359 Type-C chip driver Signed-off-by: Badhri Jagan Sridharan Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20201029063138.1429760-2-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/maxim,max33359.yaml | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/maxim,max33359.yaml diff --git a/Documentation/devicetree/bindings/usb/maxim,max33359.yaml b/Documentation/devicetree/bindings/usb/maxim,max33359.yaml new file mode 100644 index 000000000000..93a19eda610b --- /dev/null +++ b/Documentation/devicetree/bindings/usb/maxim,max33359.yaml @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/usb/maxim,max33359.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Maxim TCPCI Type-C PD controller DT bindings + +maintainers: + - Badhri Jagan Sridharan + +description: Maxim TCPCI Type-C PD controller + +properties: + compatible: + enum: + - maxim,max33359 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + connector: + type: object + $ref: ../connector/usb-connector.yaml# + description: + Properties for usb c connector. + +required: + - compatible + - reg + - interrupts + - connector + +additionalProperties: false + +examples: + - | + #include + #include + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + maxtcpc@25 { + compatible = "maxim,max33359"; + reg = <0x25>; + interrupt-parent = <&gpa8>; + interrupts = <2 IRQ_TYPE_LEVEL_LOW>; + + connector { + compatible = "usb-c-connector"; + label = "USB-C"; + data-role = "dual"; + power-role = "dual"; + try-power-role = "sink"; + self-powered; + op-sink-microwatt = <2600000>; + new-source-frs-typec-current = ; + source-pdos = ; + sink-pdos = ; + }; + }; + }; +... From a07c81ac5015ac5cbcf1f8e20b9ca2974d276b87 Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Wed, 28 Oct 2020 23:31:30 -0700 Subject: [PATCH 074/172] usb: typec: tcpci_maxim: Fix the compatible string Changing compatible string to include the part number. Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20201029063138.1429760-3-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci_maxim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.c b/drivers/usb/typec/tcpm/tcpci_maxim.c index 3b3d36f94709..c1797239bf08 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim.c @@ -482,7 +482,7 @@ MODULE_DEVICE_TABLE(i2c, max_tcpci_id); #ifdef CONFIG_OF static const struct of_device_id max_tcpci_of_match[] = { - { .compatible = "maxim,tcpc", }, + { .compatible = "maxim,max33359", }, {}, }; MODULE_DEVICE_TABLE(of, max_tcpci_of_match); From e1e52361c61afdf81d81cfbbfa3ce08971e60f50 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Mon, 16 Nov 2020 12:11:42 -0800 Subject: [PATCH 075/172] usb: typec: Add plug num_altmodes sysfs attr Add a field to the typec_plug struct to record the number of available altmodes as well as the corresponding sysfs attribute to expose this to userspace. This allows userspace to determine whether there are any remaining alternate modes left to be registered by the kernel driver. It can begin executing any policy state machine after all available alternate modes have been registered with the connector class framework. This value is set to "-1" initially, signifying that a valid number of alternate modes haven't been set for the plug. The sysfs file remains hidden as long as the attribute value is -1. We re-use the partner attribute for number_of_alternate_modes since the usage and name is similar, and update the corresponding *_show() command to support both partner and plugs. Signed-off-by: Prashant Malani Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20201116201150.2919178-4-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-typec | 9 +++ drivers/usb/typec/class.c | 77 ++++++++++++++++++++- include/linux/usb/typec.h | 1 + 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec index a195247d83ee..4eccb343fc7b 100644 --- a/Documentation/ABI/testing/sysfs-class-typec +++ b/Documentation/ABI/testing/sysfs-class-typec @@ -210,6 +210,15 @@ Description: - type-c - captive +What: /sys/class/typec/-/number_of_alternate_modes +Date: November 2020 +Contact: Prashant Malani +Description: + Shows the number of alternate modes which are advertised by the plug + associated with a particular cable during Power Delivery discovery. + This file remains hidden until a value greater than or equal to 0 + is set by Type C port driver. + What: /sys/class/typec/-cable/identity/ Date: April 2017 Contact: Heikki Krogerus diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index c7412ddbd311..e68798599ca8 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -18,6 +18,7 @@ struct typec_plug { struct device dev; enum typec_plug_index index; struct ida mode_ids; + int num_altmodes; }; struct typec_cable { @@ -536,9 +537,21 @@ static DEVICE_ATTR_RO(supports_usb_power_delivery); static ssize_t number_of_alternate_modes_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct typec_partner *p = to_typec_partner(dev); + struct typec_partner *partner; + struct typec_plug *plug; + int num_altmodes; - return sysfs_emit(buf, "%d\n", p->num_altmodes); + if (is_typec_partner(dev)) { + partner = to_typec_partner(dev); + num_altmodes = partner->num_altmodes; + } else if (is_typec_plug(dev)) { + plug = to_typec_plug(dev); + num_altmodes = plug->num_altmodes; + } else { + return 0; + } + + return sysfs_emit(buf, "%d\n", num_altmodes); } static DEVICE_ATTR_RO(number_of_alternate_modes); @@ -726,11 +739,70 @@ static void typec_plug_release(struct device *dev) kfree(plug); } +static struct attribute *typec_plug_attrs[] = { + &dev_attr_number_of_alternate_modes.attr, + NULL +}; + +static umode_t typec_plug_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct typec_plug *plug = to_typec_plug(kobj_to_dev(kobj)); + + if (attr == &dev_attr_number_of_alternate_modes.attr) { + if (plug->num_altmodes < 0) + return 0; + } + + return attr->mode; +} + +static struct attribute_group typec_plug_group = { + .is_visible = typec_plug_attr_is_visible, + .attrs = typec_plug_attrs +}; + +static const struct attribute_group *typec_plug_groups[] = { + &typec_plug_group, + NULL +}; + static const struct device_type typec_plug_dev_type = { .name = "typec_plug", + .groups = typec_plug_groups, .release = typec_plug_release, }; +/** + * typec_plug_set_num_altmodes - Set the number of available plug altmodes + * @plug: The plug to be updated. + * @num_altmodes: The number of altmodes we want to specify as available. + * + * This routine is used to report the number of alternate modes supported by the + * plug. This value is *not* enforced in alternate mode registration routines. + * + * @plug.num_altmodes is set to -1 on plug registration, denoting that + * a valid value has not been set for it yet. + * + * Returns 0 on success or negative error number on failure. + */ +int typec_plug_set_num_altmodes(struct typec_plug *plug, int num_altmodes) +{ + int ret; + + if (num_altmodes < 0) + return -EINVAL; + + plug->num_altmodes = num_altmodes; + ret = sysfs_update_group(&plug->dev.kobj, &typec_plug_group); + if (ret < 0) + return ret; + + sysfs_notify(&plug->dev.kobj, NULL, "number_of_alternate_modes"); + + return 0; +} +EXPORT_SYMBOL_GPL(typec_plug_set_num_altmodes); + /** * typec_plug_register_altmode - Register USB Type-C Cable Plug Alternate Mode * @plug: USB Type-C Cable Plug that supports the alternate mode @@ -776,6 +848,7 @@ struct typec_plug *typec_register_plug(struct typec_cable *cable, sprintf(name, "plug%d", desc->index); ida_init(&plug->mode_ids); + plug->num_altmodes = -1; plug->index = desc->index; plug->dev.class = typec_class; plug->dev.parent = &cable->dev; diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index bc6b1a71cb8a..54475323f83b 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -130,6 +130,7 @@ int typec_partner_set_num_altmodes(struct typec_partner *partner, int num_altmod struct typec_altmode *typec_partner_register_altmode(struct typec_partner *partner, const struct typec_altmode_desc *desc); +int typec_plug_set_num_altmodes(struct typec_plug *plug, int num_altmodes); struct typec_altmode *typec_plug_register_altmode(struct typec_plug *plug, const struct typec_altmode_desc *desc); From 52a0372a38b45899368b44147db52f7360aaea31 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Thu, 19 Nov 2020 22:35:22 -0800 Subject: [PATCH 076/172] usb: typec: Fix num_altmodes kernel-doc error The commit to introduce the num_altmodes attribute for partner had an error where one of the parameters was named differently in the comment and the function signature. Fix the version in the comment to align with what is in the function signature. This fixes the following htmldocs warning: drivers/usb/typec/class.c:632: warning: Excess function parameter 'num_alt_modes' description in 'typec_partner_set_num_altmodes' Fixes: a0ccdc4a77a1 ("usb: typec: Add number of altmodes partner attr") Reviewed-by: Heikki Krogerus Signed-off-by: Prashant Malani Link: https://lore.kernel.org/r/20201120063523.4159877-1-pmalani@chromium.org Reported-by: Stephen Rothwell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/class.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index e68798599ca8..cb1362187a7c 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -618,7 +618,7 @@ EXPORT_SYMBOL_GPL(typec_partner_set_identity); /** * typec_partner_set_num_altmodes - Set the number of available partner altmodes * @partner: The partner to be updated. - * @num_alt_modes: The number of altmodes we want to specify as available. + * @num_altmodes: The number of altmodes we want to specify as available. * * This routine is used to report the number of alternate modes supported by the * partner. This value is *not* enforced in alternate mode registration routines. From 053af9e6e817fe58191b0d27bc67ba329d94ced2 Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Thu, 19 Nov 2020 20:53:00 -0800 Subject: [PATCH 077/172] USB: serial: mos7720: defer state restore to a workqueue The parallel port restore operation currently defers writes to a tasklet, if it sees a locked disconnect mutex. The driver goes to a lot of trouble to ensure writes happen in a non-blocking context, but things can be greatly simplified if it's done in regular process context and this is not a system performance critical path. As such, instead of doing the state restore writes in softirq context, use a workqueue and just do regular synchronous writes. In addition to the cleanup, this also imposes less on the overall system as tasklets have been deprecated because of it's softirq implications, potentially blocking a higher priority task from running. Signed-off-by: Davidlohr Bueso Link: https://lore.kernel.org/r/20201120045300.28804-1-dave@stgolabs.net [johan: amend commit message ("softirq context")] Signed-off-by: Johan Hovold --- drivers/usb/serial/mos7720.c | 234 ++++++----------------------------- 1 file changed, 35 insertions(+), 199 deletions(-) diff --git a/drivers/usb/serial/mos7720.c b/drivers/usb/serial/mos7720.c index 5a5d2a95070e..41ee2984a0df 100644 --- a/drivers/usb/serial/mos7720.c +++ b/drivers/usb/serial/mos7720.c @@ -79,14 +79,6 @@ MODULE_DEVICE_TABLE(usb, id_table); #define DCR_INIT_VAL 0x0c /* SLCTIN, nINIT */ #define ECR_INIT_VAL 0x00 /* SPP mode */ -struct urbtracker { - struct mos7715_parport *mos_parport; - struct list_head urblist_entry; - struct kref ref_count; - struct urb *urb; - struct usb_ctrlrequest *setup; -}; - enum mos7715_pp_modes { SPP = 0<<5, PS2 = 1<<5, /* moschip calls this 'NIBBLE' mode */ @@ -96,12 +88,9 @@ enum mos7715_pp_modes { struct mos7715_parport { struct parport *pp; /* back to containing struct */ struct kref ref_count; /* to instance of this struct */ - struct list_head deferred_urbs; /* list deferred async urbs */ - struct list_head active_urbs; /* list async urbs in flight */ - spinlock_t listlock; /* protects list access */ bool msg_pending; /* usb sync call pending */ struct completion syncmsg_compl; /* usb sync call completed */ - struct tasklet_struct urb_tasklet; /* for sending deferred urbs */ + struct work_struct work; /* restore deferred writes */ struct usb_serial *serial; /* back to containing struct */ __u8 shadowECR; /* parallel port regs... */ __u8 shadowDCR; @@ -265,174 +254,8 @@ static void destroy_mos_parport(struct kref *kref) kfree(mos_parport); } -static void destroy_urbtracker(struct kref *kref) -{ - struct urbtracker *urbtrack = - container_of(kref, struct urbtracker, ref_count); - struct mos7715_parport *mos_parport = urbtrack->mos_parport; - - usb_free_urb(urbtrack->urb); - kfree(urbtrack->setup); - kfree(urbtrack); - kref_put(&mos_parport->ref_count, destroy_mos_parport); -} - /* - * This runs as a tasklet when sending an urb in a non-blocking parallel - * port callback had to be deferred because the disconnect mutex could not be - * obtained at the time. - */ -static void send_deferred_urbs(struct tasklet_struct *t) -{ - int ret_val; - unsigned long flags; - struct mos7715_parport *mos_parport = from_tasklet(mos_parport, t, - urb_tasklet); - struct urbtracker *urbtrack, *tmp; - struct list_head *cursor, *next; - struct device *dev; - - /* if release function ran, game over */ - if (unlikely(mos_parport->serial == NULL)) - return; - - dev = &mos_parport->serial->dev->dev; - - /* try again to get the mutex */ - if (!mutex_trylock(&mos_parport->serial->disc_mutex)) { - dev_dbg(dev, "%s: rescheduling tasklet\n", __func__); - tasklet_schedule(&mos_parport->urb_tasklet); - return; - } - - /* if device disconnected, game over */ - if (unlikely(mos_parport->serial->disconnected)) { - mutex_unlock(&mos_parport->serial->disc_mutex); - return; - } - - spin_lock_irqsave(&mos_parport->listlock, flags); - if (list_empty(&mos_parport->deferred_urbs)) { - spin_unlock_irqrestore(&mos_parport->listlock, flags); - mutex_unlock(&mos_parport->serial->disc_mutex); - dev_dbg(dev, "%s: deferred_urbs list empty\n", __func__); - return; - } - - /* move contents of deferred_urbs list to active_urbs list and submit */ - list_for_each_safe(cursor, next, &mos_parport->deferred_urbs) - list_move_tail(cursor, &mos_parport->active_urbs); - list_for_each_entry_safe(urbtrack, tmp, &mos_parport->active_urbs, - urblist_entry) { - ret_val = usb_submit_urb(urbtrack->urb, GFP_ATOMIC); - dev_dbg(dev, "%s: urb submitted\n", __func__); - if (ret_val) { - dev_err(dev, "usb_submit_urb() failed: %d\n", ret_val); - list_del(&urbtrack->urblist_entry); - kref_put(&urbtrack->ref_count, destroy_urbtracker); - } - } - spin_unlock_irqrestore(&mos_parport->listlock, flags); - mutex_unlock(&mos_parport->serial->disc_mutex); -} - -/* callback for parallel port control urbs submitted asynchronously */ -static void async_complete(struct urb *urb) -{ - struct urbtracker *urbtrack = urb->context; - int status = urb->status; - unsigned long flags; - - if (unlikely(status)) - dev_dbg(&urb->dev->dev, "%s - nonzero urb status received: %d\n", __func__, status); - - /* remove the urbtracker from the active_urbs list */ - spin_lock_irqsave(&urbtrack->mos_parport->listlock, flags); - list_del(&urbtrack->urblist_entry); - spin_unlock_irqrestore(&urbtrack->mos_parport->listlock, flags); - kref_put(&urbtrack->ref_count, destroy_urbtracker); -} - -static int write_parport_reg_nonblock(struct mos7715_parport *mos_parport, - enum mos_regs reg, __u8 data) -{ - struct urbtracker *urbtrack; - int ret_val; - unsigned long flags; - struct usb_serial *serial = mos_parport->serial; - struct usb_device *usbdev = serial->dev; - - /* create and initialize the control urb and containing urbtracker */ - urbtrack = kmalloc(sizeof(struct urbtracker), GFP_ATOMIC); - if (!urbtrack) - return -ENOMEM; - - urbtrack->urb = usb_alloc_urb(0, GFP_ATOMIC); - if (!urbtrack->urb) { - kfree(urbtrack); - return -ENOMEM; - } - urbtrack->setup = kmalloc(sizeof(*urbtrack->setup), GFP_ATOMIC); - if (!urbtrack->setup) { - usb_free_urb(urbtrack->urb); - kfree(urbtrack); - return -ENOMEM; - } - urbtrack->setup->bRequestType = (__u8)0x40; - urbtrack->setup->bRequest = (__u8)0x0e; - urbtrack->setup->wValue = cpu_to_le16(get_reg_value(reg, dummy)); - urbtrack->setup->wIndex = cpu_to_le16(get_reg_index(reg)); - urbtrack->setup->wLength = 0; - usb_fill_control_urb(urbtrack->urb, usbdev, - usb_sndctrlpipe(usbdev, 0), - (unsigned char *)urbtrack->setup, - NULL, 0, async_complete, urbtrack); - kref_get(&mos_parport->ref_count); - urbtrack->mos_parport = mos_parport; - kref_init(&urbtrack->ref_count); - INIT_LIST_HEAD(&urbtrack->urblist_entry); - - /* - * get the disconnect mutex, or add tracker to the deferred_urbs list - * and schedule a tasklet to try again later - */ - if (!mutex_trylock(&serial->disc_mutex)) { - spin_lock_irqsave(&mos_parport->listlock, flags); - list_add_tail(&urbtrack->urblist_entry, - &mos_parport->deferred_urbs); - spin_unlock_irqrestore(&mos_parport->listlock, flags); - tasklet_schedule(&mos_parport->urb_tasklet); - dev_dbg(&usbdev->dev, "tasklet scheduled\n"); - return 0; - } - - /* bail if device disconnected */ - if (serial->disconnected) { - kref_put(&urbtrack->ref_count, destroy_urbtracker); - mutex_unlock(&serial->disc_mutex); - return -ENODEV; - } - - /* add the tracker to the active_urbs list and submit */ - spin_lock_irqsave(&mos_parport->listlock, flags); - list_add_tail(&urbtrack->urblist_entry, &mos_parport->active_urbs); - spin_unlock_irqrestore(&mos_parport->listlock, flags); - ret_val = usb_submit_urb(urbtrack->urb, GFP_ATOMIC); - mutex_unlock(&serial->disc_mutex); - if (ret_val) { - dev_err(&usbdev->dev, - "%s: submit_urb() failed: %d\n", __func__, ret_val); - spin_lock_irqsave(&mos_parport->listlock, flags); - list_del(&urbtrack->urblist_entry); - spin_unlock_irqrestore(&mos_parport->listlock, flags); - kref_put(&urbtrack->ref_count, destroy_urbtracker); - return ret_val; - } - return 0; -} - -/* - * This is the the common top part of all parallel port callback operations that + * This is the common top part of all parallel port callback operations that * send synchronous messages to the device. This implements convoluted locking * that avoids two scenarios: (1) a port operation is called after usbserial * has called our release function, at which point struct mos7715_parport has @@ -458,6 +281,10 @@ static int parport_prologue(struct parport *pp) reinit_completion(&mos_parport->syncmsg_compl); spin_unlock(&release_lock); + /* ensure writes from restore are submitted before new requests */ + if (work_pending(&mos_parport->work)) + flush_work(&mos_parport->work); + mutex_lock(&mos_parport->serial->disc_mutex); if (mos_parport->serial->disconnected) { /* device disconnected */ @@ -482,6 +309,26 @@ static inline void parport_epilogue(struct parport *pp) complete(&mos_parport->syncmsg_compl); } +static void deferred_restore_writes(struct work_struct *work) +{ + struct mos7715_parport *mos_parport; + + mos_parport = container_of(work, struct mos7715_parport, work); + + mutex_lock(&mos_parport->serial->disc_mutex); + + /* if device disconnected, game over */ + if (mos_parport->serial->disconnected) + goto done; + + write_mos_reg(mos_parport->serial, dummy, MOS7720_DCR, + mos_parport->shadowDCR); + write_mos_reg(mos_parport->serial, dummy, MOS7720_ECR, + mos_parport->shadowECR); +done: + mutex_unlock(&mos_parport->serial->disc_mutex); +} + static void parport_mos7715_write_data(struct parport *pp, unsigned char d) { struct mos7715_parport *mos_parport = pp->private_data; @@ -641,10 +488,8 @@ static void parport_mos7715_restore_state(struct parport *pp, } mos_parport->shadowDCR = s->u.pc.ctr; mos_parport->shadowECR = s->u.pc.ecr; - write_parport_reg_nonblock(mos_parport, MOS7720_DCR, - mos_parport->shadowDCR); - write_parport_reg_nonblock(mos_parport, MOS7720_ECR, - mos_parport->shadowECR); + + schedule_work(&mos_parport->work); spin_unlock(&release_lock); } @@ -714,12 +559,9 @@ static int mos7715_parport_init(struct usb_serial *serial) mos_parport->msg_pending = false; kref_init(&mos_parport->ref_count); - spin_lock_init(&mos_parport->listlock); - INIT_LIST_HEAD(&mos_parport->active_urbs); - INIT_LIST_HEAD(&mos_parport->deferred_urbs); usb_set_serial_data(serial, mos_parport); /* hijack private pointer */ mos_parport->serial = serial; - tasklet_setup(&mos_parport->urb_tasklet, send_deferred_urbs); + INIT_WORK(&mos_parport->work, deferred_restore_writes); init_completion(&mos_parport->syncmsg_compl); /* cycle parallel port reset bit */ @@ -1869,8 +1711,6 @@ static void mos7720_release(struct usb_serial *serial) if (le16_to_cpu(serial->dev->descriptor.idProduct) == MOSCHIP_DEVICE_ID_7715) { - struct urbtracker *urbtrack; - unsigned long flags; struct mos7715_parport *mos_parport = usb_get_serial_data(serial); @@ -1883,21 +1723,17 @@ static void mos7720_release(struct usb_serial *serial) if (mos_parport->msg_pending) wait_for_completion_timeout(&mos_parport->syncmsg_compl, msecs_to_jiffies(MOS_WDR_TIMEOUT)); + /* + * If delayed work is currently scheduled, wait for it to + * complete. This also implies barriers that ensure the + * below serial clearing is not hoisted above the ->work. + */ + cancel_work_sync(&mos_parport->work); parport_remove_port(mos_parport->pp); usb_set_serial_data(serial, NULL); mos_parport->serial = NULL; - /* if tasklet currently scheduled, wait for it to complete */ - tasklet_kill(&mos_parport->urb_tasklet); - - /* unlink any urbs sent by the tasklet */ - spin_lock_irqsave(&mos_parport->listlock, flags); - list_for_each_entry(urbtrack, - &mos_parport->active_urbs, - urblist_entry) - usb_unlink_urb(urbtrack->urb); - spin_unlock_irqrestore(&mos_parport->listlock, flags); parport_del_port(mos_parport->pp); kref_put(&mos_parport->ref_count, destroy_mos_parport); From 93c747ed00c1c74316645f7761f0cdb3f3d3952d Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Fri, 20 Nov 2020 12:40:27 -0600 Subject: [PATCH 078/172] usb: Fix fall-through warnings for Clang In preparation to enable -Wimplicit-fallthrough for Clang, fix multiple warnings by explicitly adding multiple break/return/fallthrough statements instead of letting the code fall through to the next case. Link: https://github.com/KSPP/linux/issues/115 Signed-off-by: Gustavo A. R. Silva Link: https://lore.kernel.org/r/a76da7ca5b4f41c13d27b298accb8222d0b04e61.1605896060.git.gustavoars@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_fs.c | 2 ++ drivers/usb/gadget/function/f_loopback.c | 2 +- drivers/usb/gadget/function/f_sourcesink.c | 1 + drivers/usb/gadget/udc/dummy_hcd.c | 2 ++ drivers/usb/host/fotg210-hcd.c | 2 +- drivers/usb/host/isp116x-hcd.c | 1 + drivers/usb/host/max3421-hcd.c | 1 + drivers/usb/host/oxu210hp-hcd.c | 1 + drivers/usb/misc/yurex.c | 1 + drivers/usb/musb/tusb6010.c | 1 + drivers/usb/storage/ene_ub6250.c | 1 + drivers/usb/storage/uas.c | 1 + 12 files changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 046f770a76da..7f9c4e35d3db 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -678,6 +678,8 @@ static __poll_t ffs_ep0_poll(struct file *file, poll_table *wait) mask |= (EPOLLIN | EPOLLOUT); break; } + break; + case FFS_CLOSING: break; case FFS_DEACTIVATED: diff --git a/drivers/usb/gadget/function/f_loopback.c b/drivers/usb/gadget/function/f_loopback.c index 1803646b3678..b56ad7c3838b 100644 --- a/drivers/usb/gadget/function/f_loopback.c +++ b/drivers/usb/gadget/function/f_loopback.c @@ -274,7 +274,7 @@ static void loopback_complete(struct usb_ep *ep, struct usb_request *req) default: ERROR(cdev, "%s loop complete --> %d, %d/%d\n", ep->name, status, req->actual, req->length); - /* FALLTHROUGH */ + fallthrough; /* NOTE: since this driver doesn't maintain an explicit record * of requests it submitted (just maintains qlen count), we diff --git a/drivers/usb/gadget/function/f_sourcesink.c b/drivers/usb/gadget/function/f_sourcesink.c index ed68a4860b7d..5a201ba7b155 100644 --- a/drivers/usb/gadget/function/f_sourcesink.c +++ b/drivers/usb/gadget/function/f_sourcesink.c @@ -559,6 +559,7 @@ static void source_sink_complete(struct usb_ep *ep, struct usb_request *req) #if 1 DBG(cdev, "%s complete --> %d, %d/%d\n", ep->name, status, req->actual, req->length); + break; #endif case -EREMOTEIO: /* short read */ break; diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index 1dde016ca86f..f6b407778179 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -553,6 +553,7 @@ static int dummy_enable(struct usb_ep *_ep, /* we'll fake any legal size */ break; /* save a return statement */ + fallthrough; default: goto done; } @@ -595,6 +596,7 @@ static int dummy_enable(struct usb_ep *_ep, if (max <= 1023) break; /* save a return statement */ + fallthrough; default: goto done; } diff --git a/drivers/usb/host/fotg210-hcd.c b/drivers/usb/host/fotg210-hcd.c index 1d94fcfac2c2..0451943f0bc4 100644 --- a/drivers/usb/host/fotg210-hcd.c +++ b/drivers/usb/host/fotg210-hcd.c @@ -5276,7 +5276,7 @@ static int fotg210_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, */ if (urb->transfer_buffer_length > (16 * 1024)) return -EMSGSIZE; - /* FALLTHROUGH */ + fallthrough; /* case PIPE_BULK: */ default: if (!qh_urb_transaction(fotg210, urb, &qtd_list, mem_flags)) diff --git a/drivers/usb/host/isp116x-hcd.c b/drivers/usb/host/isp116x-hcd.c index 3055d9abfec3..8544a2a2c1e6 100644 --- a/drivers/usb/host/isp116x-hcd.c +++ b/drivers/usb/host/isp116x-hcd.c @@ -1447,6 +1447,7 @@ static int isp116x_bus_resume(struct usb_hcd *hcd) val &= ~HCCONTROL_HCFS; val |= HCCONTROL_USB_RESUME; isp116x_write_reg32(isp116x, HCCONTROL, val); + break; case HCCONTROL_USB_RESUME: break; case HCCONTROL_USB_OPER: diff --git a/drivers/usb/host/max3421-hcd.c b/drivers/usb/host/max3421-hcd.c index ebb8180b52ab..afd9174d83b1 100644 --- a/drivers/usb/host/max3421-hcd.c +++ b/drivers/usb/host/max3421-hcd.c @@ -1537,6 +1537,7 @@ max3421_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) __func__, urb->interval); return -EINVAL; } + break; default: break; } diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index 27dbbe1b28b1..aa42df39e6a1 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -1365,6 +1365,7 @@ __acquires(oxu->lock) switch (urb->status) { case -EINPROGRESS: /* success */ urb->status = 0; + break; default: /* fault */ break; case -EREMOTEIO: /* fault or normal */ diff --git a/drivers/usb/misc/yurex.c b/drivers/usb/misc/yurex.c index e3165d79b5f6..73ebfa6e9715 100644 --- a/drivers/usb/misc/yurex.c +++ b/drivers/usb/misc/yurex.c @@ -137,6 +137,7 @@ static void yurex_interrupt(struct urb *urb) dev_err(&dev->interface->dev, "%s - overflow with length %d, actual length is %d\n", __func__, YUREX_BUF_SIZE, dev->urb->actual_length); + return; case -ECONNRESET: case -ENOENT: case -ESHUTDOWN: diff --git a/drivers/usb/musb/tusb6010.c b/drivers/usb/musb/tusb6010.c index c26683a2702b..c42937692207 100644 --- a/drivers/usb/musb/tusb6010.c +++ b/drivers/usb/musb/tusb6010.c @@ -467,6 +467,7 @@ static void musb_do_idle(struct timer_list *t) fallthrough; case OTG_STATE_A_IDLE: tusb_musb_set_vbus(musb, 0); + break; default: break; } diff --git a/drivers/usb/storage/ene_ub6250.c b/drivers/usb/storage/ene_ub6250.c index 98c1aa594e6c..5f7d678502be 100644 --- a/drivers/usb/storage/ene_ub6250.c +++ b/drivers/usb/storage/ene_ub6250.c @@ -861,6 +861,7 @@ static int ms_count_freeblock(struct us_data *us, u16 PhyBlock) case MS_LB_NOT_USED: case MS_LB_NOT_USED_ERASED: Count++; + break; default: break; } diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index c8a577309e8f..6bd33c57fdcb 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -690,6 +690,7 @@ static int uas_queuecommand_lck(struct scsi_cmnd *cmnd, fallthrough; case DMA_TO_DEVICE: cmdinfo->state |= ALLOC_DATA_OUT_URB | SUBMIT_DATA_OUT_URB; + break; case DMA_NONE: break; } From f3ef38160e3dacb490483eb2104b4ce05cd97058 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 23 Nov 2020 11:23:46 +0100 Subject: [PATCH 079/172] usb: isp1301-omap: Convert to use GPIO descriptors This modernized the ISP1301 a bit by switching it to provide a GPIO descriptor from the H2 board if used. Cc: Tony Lindgren Cc: Felipe Balbi Signed-off-by: Linus Walleij Link: https://lore.kernel.org/r/20201123102346.48284-1-linus.walleij@linaro.org Signed-off-by: Greg Kroah-Hartman --- arch/arm/mach-omap1/board-h2.c | 22 ++++++++++++++++++++-- drivers/usb/phy/phy-isp1301-omap.c | 21 +++++++++++++-------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/arch/arm/mach-omap1/board-h2.c b/arch/arm/mach-omap1/board-h2.c index cb7ce627ffe8..c40cf5ef8607 100644 --- a/arch/arm/mach-omap1/board-h2.c +++ b/arch/arm/mach-omap1/board-h2.c @@ -16,6 +16,7 @@ * Copyright (C) 2004 Nokia Corporation by Imre Deak */ #include +#include #include #include #include @@ -46,6 +47,9 @@ #include "common.h" #include "board-h2.h" +/* The first 16 SoC GPIO lines are on this GPIO chip */ +#define OMAP_GPIO_LABEL "gpio-0-15" + /* At OMAP1610 Innovator the Ethernet is directly connected to CS1 */ #define OMAP1610_ETHR_START 0x04000300 @@ -334,7 +338,19 @@ static struct i2c_board_info __initdata h2_i2c_board_info[] = { I2C_BOARD_INFO("tps65010", 0x48), .platform_data = &tps_board, }, { - I2C_BOARD_INFO("isp1301_omap", 0x2d), + .type = "isp1301_omap", + .addr = 0x2d, + .dev_name = "isp1301", + }, +}; + +static struct gpiod_lookup_table isp1301_gpiod_table = { + .dev_id = "isp1301", + .table = { + /* Active low since the irq triggers on falling edge */ + GPIO_LOOKUP(OMAP_GPIO_LABEL, 2, + NULL, GPIO_ACTIVE_LOW), + { }, }, }; @@ -406,8 +422,10 @@ static void __init h2_init(void) h2_smc91x_resources[1].end = gpio_to_irq(0); platform_add_devices(h2_devices, ARRAY_SIZE(h2_devices)); omap_serial_init(); + + /* ISP1301 IRQ wired at M14 */ + omap_cfg_reg(M14_1510_GPIO2); h2_i2c_board_info[0].irq = gpio_to_irq(58); - h2_i2c_board_info[1].irq = gpio_to_irq(2); omap_register_i2c_bus(1, 100, h2_i2c_board_info, ARRAY_SIZE(h2_i2c_board_info)); omap1_usb_init(&h2_usb_config); diff --git a/drivers/usb/phy/phy-isp1301-omap.c b/drivers/usb/phy/phy-isp1301-omap.c index 4a6462c92ef2..00506fb01bda 100644 --- a/drivers/usb/phy/phy-isp1301-omap.c +++ b/drivers/usb/phy/phy-isp1301-omap.c @@ -1208,9 +1208,6 @@ static int isp1301_remove(struct i2c_client *i2c) #ifdef CONFIG_USB_OTG otg_unbind(isp); #endif - if (machine_is_omap_h2()) - gpio_free(2); - set_bit(WORK_STOP, &isp->todo); del_timer_sync(&isp->timer); flush_work(&isp->work); @@ -1480,6 +1477,7 @@ isp1301_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { int status; struct isp1301 *isp; + int irq; if (the_transceiver) return 0; @@ -1543,20 +1541,27 @@ isp1301_probe(struct i2c_client *i2c, const struct i2c_device_id *id) #endif if (machine_is_omap_h2()) { + struct gpio_desc *gpiod; + /* full speed signaling by default */ isp1301_set_bits(isp, ISP1301_MODE_CONTROL_1, MC1_SPEED); isp1301_set_bits(isp, ISP1301_MODE_CONTROL_2, MC2_SPD_SUSP_CTRL); - /* IRQ wired at M14 */ - omap_cfg_reg(M14_1510_GPIO2); - if (gpio_request(2, "isp1301") == 0) - gpio_direction_input(2); + gpiod = devm_gpiod_get(&i2c->dev, NULL, GPIOD_IN); + if (IS_ERR(gpiod)) { + dev_err(&i2c->dev, "cannot obtain H2 GPIO\n"); + goto fail; + } + gpiod_set_consumer_name(gpiod, "isp1301"); + irq = gpiod_to_irq(gpiod); isp->irq_type = IRQF_TRIGGER_FALLING; + } else { + irq = i2c->irq; } - status = request_irq(i2c->irq, isp1301_irq, + status = request_irq(irq, isp1301_irq, isp->irq_type, DRIVER_NAME, isp); if (status < 0) { dev_dbg(&i2c->dev, "can't get IRQ %d, err %d\n", From 7656ca71b0ba04ae7437afe3d046e9134442678e Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Wed, 25 Nov 2020 15:06:41 +0300 Subject: [PATCH 080/172] usb: pd: DFP product types USB Power Delivery Specification R3.0 introduced separate field for the DFP product type to the ID Header VDO. Signed-off-by: Heikki Krogerus Link: https://lore.kernel.org/r/20201125120642.37156-2-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/pd_vdo.h | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/include/linux/usb/pd_vdo.h b/include/linux/usb/pd_vdo.h index 8c5cb5830754..8c08eeb9a74b 100644 --- a/include/linux/usb/pd_vdo.h +++ b/include/linux/usb/pd_vdo.h @@ -103,17 +103,25 @@ * -------------------- * <31> :: data capable as a USB host * <30> :: data capable as a USB device - * <29:27> :: product type + * <29:27> :: product type (UFP / Cable) * <26> :: modal operation supported (1b == yes) - * <25:16> :: Reserved, Shall be set to zero + * <25:16> :: product type (DFP) * <15:0> :: USB-IF assigned VID for this cable vendor */ #define IDH_PTYPE_UNDEF 0 #define IDH_PTYPE_HUB 1 #define IDH_PTYPE_PERIPH 2 +#define IDH_PTYPE_PSD 3 +#define IDH_PTYPE_AMA 5 + #define IDH_PTYPE_PCABLE 3 #define IDH_PTYPE_ACABLE 4 -#define IDH_PTYPE_AMA 5 + +#define IDH_PTYPE_DFP_UNDEF 0 +#define IDH_PTYPE_DFP_HUB 1 +#define IDH_PTYPE_DFP_HOST 2 +#define IDH_PTYPE_DFP_PB 3 +#define IDH_PTYPE_DFP_AMC 4 #define VDO_IDH(usbh, usbd, ptype, is_modal, vid) \ ((usbh) << 31 | (usbd) << 30 | ((ptype) & 0x7) << 27 \ @@ -122,6 +130,7 @@ #define PD_IDH_PTYPE(vdo) (((vdo) >> 27) & 0x7) #define PD_IDH_VID(vdo) ((vdo) & 0xffff) #define PD_IDH_MODAL_SUPP(vdo) ((vdo) & (1 << 26)) +#define PD_IDH_DFP_PTYPE(vdo) (((vdo) >> 23) & 0x7) /* * Cert Stat VDO From fd2c35b2f59f3d4a92e8604c7105f001d1da503c Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Wed, 25 Nov 2020 00:49:09 -0800 Subject: [PATCH 081/172] usb: typec: Consolidate sysfs ABI documentation Both partner and cable have identity VDOs. These are listed separately in the Documentation/ABI/testing/sysfs-class-typec. Factor these out into a common location to avoid the duplication. Acked-by: Heikki Krogerus Signed-off-by: Prashant Malani Link: https://lore.kernel.org/r/20201125084911.1077462-1-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-typec | 59 ++++++--------------- 1 file changed, 17 insertions(+), 42 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec index 4eccb343fc7b..88ffc14d4cd2 100644 --- a/Documentation/ABI/testing/sysfs-class-typec +++ b/Documentation/ABI/testing/sysfs-class-typec @@ -147,42 +147,6 @@ Description: during Power Delivery discovery. This file remains hidden until a value greater than or equal to 0 is set by Type C port driver. -What: /sys/class/typec/-partner>/identity/ -Date: April 2017 -Contact: Heikki Krogerus -Description: - This directory appears only if the port device driver is capable - of showing the result of Discover Identity USB power delivery - command. That will not always be possible even when USB power - delivery is supported, for example when USB power delivery - communication for the port is mostly handled in firmware. If the - directory exists, it will have an attribute file for every VDO - in Discover Identity command result. - -What: /sys/class/typec/-partner/identity/id_header -Date: April 2017 -Contact: Heikki Krogerus -Description: - ID Header VDO part of Discover Identity command result. The - value will show 0 until Discover Identity command result becomes - available. The value can be polled. - -What: /sys/class/typec/-partner/identity/cert_stat -Date: April 2017 -Contact: Heikki Krogerus -Description: - Cert Stat VDO part of Discover Identity command result. The - value will show 0 until Discover Identity command result becomes - available. The value can be polled. - -What: /sys/class/typec/-partner/identity/product -Date: April 2017 -Contact: Heikki Krogerus -Description: - Product VDO part of Discover Identity command result. The value - will show 0 until Discover Identity command result becomes - available. The value can be polled. - USB Type-C cable devices (eg. /sys/class/typec/port0-cable/) @@ -219,17 +183,28 @@ Description: This file remains hidden until a value greater than or equal to 0 is set by Type C port driver. -What: /sys/class/typec/-cable/identity/ + +USB Type-C partner/cable Power Delivery Identity objects + +NOTE: The following attributes will be applicable to both +partner (e.g /sys/class/typec/port0-partner/) and +cable (e.g /sys/class/typec/port0-cable/) devices. Consequently, the example file +paths below are prefixed with "/sys/class/typec/-{partner|cable}/" to +reflect this. + +What: /sys/class/typec/-{partner|cable}/identity/ Date: April 2017 Contact: Heikki Krogerus Description: This directory appears only if the port device driver is capable of showing the result of Discover Identity USB power delivery command. That will not always be possible even when USB power - delivery is supported. If the directory exists, it will have an - attribute for every VDO returned by Discover Identity command. + delivery is supported, for example when USB power delivery + communication for the port is mostly handled in firmware. If the + directory exists, it will have an attribute file for every VDO + in Discover Identity command result. -What: /sys/class/typec/-cable/identity/id_header +What: /sys/class/typec/-{partner|cable}/identity/id_header Date: April 2017 Contact: Heikki Krogerus Description: @@ -237,7 +212,7 @@ Description: value will show 0 until Discover Identity command result becomes available. The value can be polled. -What: /sys/class/typec/-cable/identity/cert_stat +What: /sys/class/typec/-{partner|cable}/identity/cert_stat Date: April 2017 Contact: Heikki Krogerus Description: @@ -245,7 +220,7 @@ Description: value will show 0 until Discover Identity command result becomes available. The value can be polled. -What: /sys/class/typec/-cable/identity/product +What: /sys/class/typec/-{partner|cable}/identity/product Date: April 2017 Contact: Heikki Krogerus Description: From 2e70c495cadebdcc6f80fde3553401bb0987b29f Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Wed, 25 Nov 2020 00:49:11 -0800 Subject: [PATCH 082/172] usb: typec: Expose Product Type VDOs via sysfs A PD-capable device can return up to 3 Product Type VDOs as part of its DiscoverIdentity Response (USB PD Spec, Rev 3.0, Version 2.0, Section 6.4.4.3.1). Add sysfs attributes to expose these to userspace. Cc: Benson Leung Cc: Heikki Krogerus Reviewed-by: Heikki Krogerus Signed-off-by: Prashant Malani Link: https://lore.kernel.org/r/20201125084911.1077462-2-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-typec | 24 +++++++++++++++ drivers/usb/typec/class.c | 33 +++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec index 88ffc14d4cd2..619c4c67432b 100644 --- a/Documentation/ABI/testing/sysfs-class-typec +++ b/Documentation/ABI/testing/sysfs-class-typec @@ -228,6 +228,30 @@ Description: will show 0 until Discover Identity command result becomes available. The value can be polled. +What: /sys/class/typec/-{partner|cable}/identity/product_type_vdo1 +Date: October 2020 +Contact: Prashant Malani +Description: + 1st Product Type VDO of Discover Identity command result. + The value will show 0 until Discover Identity command result becomes + available and a valid Product Type VDO is returned. + +What: /sys/class/typec/-{partner|cable}/identity/product_type_vdo2 +Date: October 2020 +Contact: Prashant Malani +Description: + 2nd Product Type VDO of Discover Identity command result. + The value will show 0 until Discover Identity command result becomes + available and a valid Product Type VDO is returned. + +What: /sys/class/typec/-{partner|cable}/identity/product_type_vdo3 +Date: October 2020 +Contact: Prashant Malani +Description: + 3rd Product Type VDO of Discover Identity command result. + The value will show 0 until Discover Identity command result becomes + available and a valid Product Type VDO is returned. + USB Type-C port alternate mode devices. diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index cb1362187a7c..df4478baf95b 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -124,10 +124,40 @@ static ssize_t product_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RO(product); +static ssize_t product_type_vdo1_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sysfs_emit(buf, "0x%08x\n", id->vdo[0]); +} +static DEVICE_ATTR_RO(product_type_vdo1); + +static ssize_t product_type_vdo2_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sysfs_emit(buf, "0x%08x\n", id->vdo[1]); +} +static DEVICE_ATTR_RO(product_type_vdo2); + +static ssize_t product_type_vdo3_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sysfs_emit(buf, "0x%08x\n", id->vdo[2]); +} +static DEVICE_ATTR_RO(product_type_vdo3); + static struct attribute *usb_pd_id_attrs[] = { &dev_attr_id_header.attr, &dev_attr_cert_stat.attr, &dev_attr_product.attr, + &dev_attr_product_type_vdo1.attr, + &dev_attr_product_type_vdo2.attr, + &dev_attr_product_type_vdo3.attr, NULL }; @@ -146,6 +176,9 @@ static void typec_report_identity(struct device *dev) sysfs_notify(&dev->kobj, "identity", "id_header"); sysfs_notify(&dev->kobj, "identity", "cert_stat"); sysfs_notify(&dev->kobj, "identity", "product"); + sysfs_notify(&dev->kobj, "identity", "product_type_vdo1"); + sysfs_notify(&dev->kobj, "identity", "product_type_vdo2"); + sysfs_notify(&dev->kobj, "identity", "product_type_vdo3"); } /* ------------------------------------------------------------------------- */ From 7abc6ca5dced0b5953df179007528e39b9fc43b7 Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Tue, 24 Nov 2020 18:07:03 -0800 Subject: [PATCH 083/172] usb: typec: tcpm: Disregard vbus off while in PR_SWAP_SNK_SRC_SOURCE_ON During PR_SWAP sequence, when TCPM is waiting in PR_SWAP_SNK_SRC_SOURCE_ON for the vbus source to ramp up, TCPM would prematurely exit PR_SWAP_SNK_SRC_SOURCE_ON and transition to SNK_UNATTACHED state when a vbus off notification is received. This should not be the case as vbus can still be off while in PR_SWAP_SNK_SRC_SOURCE_ON and the vbus source has PD_T_NEWSRC to ramp up. Reviewed-by: Guenter Roeck Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20201125020703.1604979-1-badhri@google.com Acked-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 4aac0efdb720..277b9d4d9c84 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -4218,6 +4218,14 @@ static void _tcpm_pd_vbus_off(struct tcpm_port *port) /* Do nothing, expected */ break; + case PR_SWAP_SNK_SRC_SOURCE_ON: + /* + * Do nothing when vbus off notification is received. + * TCPM can wait for PD_T_NEWSRC in PR_SWAP_SNK_SRC_SOURCE_ON + * for the vbus source to ramp up. + */ + break; + case PORT_RESET_WAIT_OFF: tcpm_set_state(port, tcpm_default_state(port), 0); break; From 11e5e568ceed7c8c570313a14fa96c72f21dad31 Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Tue, 24 Nov 2020 17:48:04 -0800 Subject: [PATCH 084/172] usb: typec: tcpm: Stay in SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS till Rp is seen TD.4.7.3. Try SNK DRP Connect Try.SRC DRP fails. The compliance tester mimics being a Try.SRC USB-C port. The failure is due to TCPM exiting SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS when VBUS is not present eventhough when SNK.Rp is seen. Exit to SRC_TRYWAIT from SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS only when SNK.Rp is not seen for PD_T_TRY_CC_DEBOUNCE. >From the spec: The port shall then transition to Attached.SNK when the SNK.Rp state is detected on exactly one of the CC1 or CC2 pins for at least tTryCCDebounce and VBUS is detected. Alternatively, the port shall transition to TryWait.SRC if SNK.Rp state is not detected for tTryCCDebounce. Reviewed-by: Guenter Roeck Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20201125014804.1596719-1-badhri@google.com Acked-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 18 +++++++++++++----- include/linux/usb/pd.h | 1 + 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 277b9d4d9c84..3bbc1f10af49 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -3124,15 +3124,13 @@ static void run_state_machine(struct tcpm_port *port) break; case SNK_TRY_WAIT_DEBOUNCE: tcpm_set_state(port, SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS, - PD_T_PD_DEBOUNCE); + PD_T_TRY_CC_DEBOUNCE); break; case SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS: - if (port->vbus_present && tcpm_port_is_sink(port)) { + if (port->vbus_present && tcpm_port_is_sink(port)) tcpm_set_state(port, SNK_ATTACHED, 0); - } else { - tcpm_set_state(port, SRC_TRYWAIT, 0); + else port->max_wait = 0; - } break; case SRC_TRYWAIT: tcpm_set_cc(port, tcpm_rp_cc(port)); @@ -4053,6 +4051,12 @@ static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1, if (!tcpm_port_is_sink(port)) tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, 0); break; + case SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS: + if (!tcpm_port_is_sink(port)) + tcpm_set_state(port, SRC_TRYWAIT, PD_T_TRY_CC_DEBOUNCE); + else + tcpm_set_state(port, SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS, 0); + break; case SNK_TRYWAIT: /* Do nothing, waiting for tCCDebounce */ break; @@ -4139,6 +4143,10 @@ static void _tcpm_pd_vbus_on(struct tcpm_port *port) case SNK_TRYWAIT_DEBOUNCE: /* Do nothing, waiting for Rp */ break; + case SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS: + if (port->vbus_present && tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_ATTACHED, 0); + break; case SRC_TRY_WAIT: case SRC_TRY_DEBOUNCE: /* Do nothing, waiting for sink detection */ diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index 3a805e2ecbc9..63a66dd5d832 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -484,6 +484,7 @@ static inline unsigned int rdo_max_power(u32 rdo) #define PD_T_CC_DEBOUNCE 200 /* 100 - 200 ms */ #define PD_T_PD_DEBOUNCE 20 /* 10 - 20 ms */ +#define PD_T_TRY_CC_DEBOUNCE 15 /* 10 - 20 ms */ #define PD_N_CAPS_COUNT (PD_T_NO_RESPONSE / PD_T_SEND_SOURCE_CAP) #define PD_N_HARD_RESET_COUNT 2 From 4154a4f70a9488212f8731770e10eae957d33da9 Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Wed, 25 Nov 2020 17:24:58 +0100 Subject: [PATCH 085/172] USB: core: Constify static attribute_group structs These are never modified, so make them const to allow the compiler to put them in read-only memory. Done with the help of coccinelle. Signed-off-by: Rikard Falkeborn Link: https://lore.kernel.org/r/20201125162500.37228-2-rikard.falkeborn@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/endpoint.c | 2 +- drivers/usb/core/port.c | 4 ++-- drivers/usb/core/sysfs.c | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/usb/core/endpoint.c b/drivers/usb/core/endpoint.c index 1c2c04079676..903426b6d305 100644 --- a/drivers/usb/core/endpoint.c +++ b/drivers/usb/core/endpoint.c @@ -153,7 +153,7 @@ static struct attribute *ep_dev_attrs[] = { &dev_attr_direction.attr, NULL, }; -static struct attribute_group ep_dev_attr_grp = { +static const struct attribute_group ep_dev_attr_grp = { .attrs = ep_dev_attrs, }; static const struct attribute_group *ep_dev_groups[] = { diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index 235a7c645503..dfcca9c876c7 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -155,7 +155,7 @@ static struct attribute *port_dev_attrs[] = { NULL, }; -static struct attribute_group port_dev_attr_grp = { +static const struct attribute_group port_dev_attr_grp = { .attrs = port_dev_attrs, }; @@ -169,7 +169,7 @@ static struct attribute *port_dev_usb3_attrs[] = { NULL, }; -static struct attribute_group port_dev_usb3_attr_grp = { +static const struct attribute_group port_dev_usb3_attr_grp = { .attrs = port_dev_usb3_attrs, }; diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 8d134193fa0c..d85699bee671 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -641,7 +641,7 @@ static struct attribute *usb2_hardware_lpm_attr[] = { &dev_attr_usb2_lpm_besl.attr, NULL, }; -static struct attribute_group usb2_hardware_lpm_attr_group = { +static const struct attribute_group usb2_hardware_lpm_attr_group = { .name = power_group_name, .attrs = usb2_hardware_lpm_attr, }; @@ -651,7 +651,7 @@ static struct attribute *usb3_hardware_lpm_attr[] = { &dev_attr_usb3_hardware_lpm_u2.attr, NULL, }; -static struct attribute_group usb3_hardware_lpm_attr_group = { +static const struct attribute_group usb3_hardware_lpm_attr_group = { .name = power_group_name, .attrs = usb3_hardware_lpm_attr, }; @@ -663,7 +663,7 @@ static struct attribute *power_attrs[] = { &dev_attr_active_duration.attr, NULL, }; -static struct attribute_group power_attr_group = { +static const struct attribute_group power_attr_group = { .name = power_group_name, .attrs = power_attrs, }; @@ -832,7 +832,7 @@ static struct attribute *dev_attrs[] = { #endif NULL, }; -static struct attribute_group dev_attr_grp = { +static const struct attribute_group dev_attr_grp = { .attrs = dev_attrs, }; @@ -865,7 +865,7 @@ static umode_t dev_string_attrs_are_visible(struct kobject *kobj, return a->mode; } -static struct attribute_group dev_string_attr_grp = { +static const struct attribute_group dev_string_attr_grp = { .attrs = dev_string_attrs, .is_visible = dev_string_attrs_are_visible, }; @@ -1222,7 +1222,7 @@ static struct attribute *intf_attrs[] = { &dev_attr_interface_authorized.attr, NULL, }; -static struct attribute_group intf_attr_grp = { +static const struct attribute_group intf_attr_grp = { .attrs = intf_attrs, }; @@ -1246,7 +1246,7 @@ static umode_t intf_assoc_attrs_are_visible(struct kobject *kobj, return a->mode; } -static struct attribute_group intf_assoc_attr_grp = { +static const struct attribute_group intf_assoc_attr_grp = { .attrs = intf_assoc_attrs, .is_visible = intf_assoc_attrs_are_visible, }; From 690756a367649a19c5622d135e00799930e7996c Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Wed, 25 Nov 2020 17:24:59 +0100 Subject: [PATCH 086/172] usb: typec: Constify static attribute_group structs These are never modified, so make them const to allow the compiler to put them in read-only memory. Done with the help of coccinelle. Signed-off-by: Rikard Falkeborn Link: https://lore.kernel.org/r/20201125162500.37228-3-rikard.falkeborn@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/class.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index df4478baf95b..8f40093c2044 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -417,7 +417,7 @@ static umode_t typec_altmode_attr_is_visible(struct kobject *kobj, return attr->mode; } -static struct attribute_group typec_altmode_group = { +static const struct attribute_group typec_altmode_group = { .is_visible = typec_altmode_attr_is_visible, .attrs = typec_altmode_attrs, }; @@ -607,7 +607,7 @@ static umode_t typec_partner_attr_is_visible(struct kobject *kobj, struct attrib return attr->mode; } -static struct attribute_group typec_partner_group = { +static const struct attribute_group typec_partner_group = { .is_visible = typec_partner_attr_is_visible, .attrs = typec_partner_attrs }; @@ -789,7 +789,7 @@ static umode_t typec_plug_attr_is_visible(struct kobject *kobj, struct attribute return attr->mode; } -static struct attribute_group typec_plug_group = { +static const struct attribute_group typec_plug_group = { .is_visible = typec_plug_attr_is_visible, .attrs = typec_plug_attrs }; @@ -1479,7 +1479,7 @@ static umode_t typec_attr_is_visible(struct kobject *kobj, return attr->mode; } -static struct attribute_group typec_group = { +static const struct attribute_group typec_group = { .is_visible = typec_attr_is_visible, .attrs = typec_attrs, }; From 52170e937866b3bac1572ed48a97d370f42e52fa Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Wed, 25 Nov 2020 17:25:00 +0100 Subject: [PATCH 087/172] usb: common: ulpi: Constify static attribute_group struct It is never modified, so make them const to allow the compiler to put it in read-only memory. Done with the help of coccinelle. Signed-off-by: Rikard Falkeborn Link: https://lore.kernel.org/r/20201125162500.37228-4-rikard.falkeborn@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/common/ulpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/common/ulpi.c b/drivers/usb/common/ulpi.c index a18d7c4222dd..ce5e6f6711f7 100644 --- a/drivers/usb/common/ulpi.c +++ b/drivers/usb/common/ulpi.c @@ -118,7 +118,7 @@ static struct attribute *ulpi_dev_attrs[] = { NULL }; -static struct attribute_group ulpi_dev_attr_group = { +static const struct attribute_group ulpi_dev_attr_group = { .attrs = ulpi_dev_attrs, }; From ab37fa851c488be805f6568ecaabb67b13cd937c Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Thu, 26 Nov 2020 14:57:35 +0300 Subject: [PATCH 088/172] usb: typec: Add type sysfs attribute file for partners USB Power Delivery Specification defines a set of product types for partners and cables. The product type can be read from the ID Header VDO which is the first object in the response to the Discover Identity command. This attribute will display the product type of the partner. The cables already have the attribute. This sysfs attribute file is only created for the partners and cables if the product type is really known in the driver. Some interfaces do not give access to the Discover Identity response from the partner or cable, but they may still supply the product type separately in some cases. When the product type of the partner or cable is detected, uevent is also raised with PRODUCT_TYPE set to show the actual product type (for example PRODUCT_TYPE=host). Signed-off-by: Heikki Krogerus Link: https://lore.kernel.org/r/20201126115735.50529-1-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-typec | 64 ++++++++++- drivers/usb/typec/class.c | 118 +++++++++++++++++--- 2 files changed, 165 insertions(+), 17 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec index 619c4c67432b..8eab41e79ce6 100644 --- a/Documentation/ABI/testing/sysfs-class-typec +++ b/Documentation/ABI/testing/sysfs-class-typec @@ -147,6 +147,52 @@ Description: during Power Delivery discovery. This file remains hidden until a value greater than or equal to 0 is set by Type C port driver. +What: /sys/class/typec/-partner/type +Date: December 2020 +Contact: Heikki Krogerus +Description: USB Power Delivery Specification defines a set of product types + for the partner devices. This file will show the product type of + the partner if it is known. Dual-role capable partners will have + both UFP and DFP product types defined, but only one that + matches the current role will be active at the time. If the + product type of the partner is not visible to the device driver, + this file will not exist. + + When the partner product type is detected, or changed with role + swap, uvevent is also raised that contains PRODUCT_TYPE= (for example PRODUCT_TYPE=hub). + + Valid values: + + UFP / device role + ====================== ========================== + undefined - + hub PDUSB Hub + peripheral PDUSB Peripheral + psd Power Bank + ama Alternate Mode Adapter + ====================== ========================== + + DFP / host role + ====================== ========================== + undefined - + hub PDUSB Hub + host PDUSB Host + power_brick Power Brick + amc Alternate Mode Controller + ====================== ========================== + +What: /sys/class/typec/-partner>/identity/ +Date: April 2017 +Contact: Heikki Krogerus +Description: + This directory appears only if the port device driver is capable + of showing the result of Discover Identity USB power delivery + command. That will not always be possible even when USB power + delivery is supported, for example when USB power delivery + communication for the port is mostly handled in firmware. If the + directory exists, it will have an attribute file for every VDO + in Discover Identity command result. USB Type-C cable devices (eg. /sys/class/typec/port0-cable/) @@ -159,9 +205,21 @@ described in USB Type-C and USB Power Delivery specifications. What: /sys/class/typec/-cable/type Date: April 2017 Contact: Heikki Krogerus -Description: - Shows if the cable is active. - Valid values: active, passive +Description: USB Power Delivery Specification defines a set of product types + for the cables. This file will show the product type of the + cable if it is known. If the product type of the cable is not + visible to the device driver, this file will not exist. + + When the cable product type is detected, uvevent is also raised + with PRODUCT_TYPE showing the product type of the cable. + + Valid values: + + ====================== ========================== + undefined - + active Active Cable + passive Passive Cable + ====================== ========================== What: /sys/class/typec/-cable/plug_type Date: April 2017 diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 8f40093c2044..4f6e58dfb81d 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "bus.h" @@ -83,6 +84,29 @@ static const char * const typec_accessory_modes[] = { [TYPEC_ACCESSORY_DEBUG] = "debug", }; +/* Product types defined in USB PD Specification R3.0 V2.0 */ +static const char * const product_type_ufp[8] = { + [IDH_PTYPE_UNDEF] = "undefined", + [IDH_PTYPE_HUB] = "hub", + [IDH_PTYPE_PERIPH] = "peripheral", + [IDH_PTYPE_PSD] = "psd", + [IDH_PTYPE_AMA] = "ama", +}; + +static const char * const product_type_dfp[8] = { + [IDH_PTYPE_DFP_UNDEF] = "undefined", + [IDH_PTYPE_DFP_HUB] = "hub", + [IDH_PTYPE_DFP_HOST] = "host", + [IDH_PTYPE_DFP_PB] = "power_brick", + [IDH_PTYPE_DFP_AMC] = "amc", +}; + +static const char * const product_type_cable[8] = { + [IDH_PTYPE_UNDEF] = "undefined", + [IDH_PTYPE_PCABLE] = "passive", + [IDH_PTYPE_ACABLE] = "active", +}; + static struct usb_pd_identity *get_pd_identity(struct device *dev) { if (is_typec_partner(dev)) { @@ -97,6 +121,32 @@ static struct usb_pd_identity *get_pd_identity(struct device *dev) return NULL; } +static const char *get_pd_product_type(struct device *dev) +{ + struct typec_port *port = to_typec_port(dev->parent); + struct usb_pd_identity *id = get_pd_identity(dev); + const char *ptype = NULL; + + if (is_typec_partner(dev)) { + if (!id) + return NULL; + + if (port->data_role == TYPEC_HOST) + ptype = product_type_ufp[PD_IDH_PTYPE(id->id_header)]; + else + ptype = product_type_dfp[PD_IDH_DFP_PTYPE(id->id_header)]; + } else if (is_typec_cable(dev)) { + if (id) + ptype = product_type_cable[PD_IDH_PTYPE(id->id_header)]; + else + ptype = to_typec_cable(dev)->active ? + product_type_cable[IDH_PTYPE_ACABLE] : + product_type_cable[IDH_PTYPE_PCABLE]; + } + + return ptype; +} + static ssize_t id_header_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -171,6 +221,25 @@ static const struct attribute_group *usb_pd_id_groups[] = { NULL, }; +static void typec_product_type_notify(struct device *dev) +{ + char *envp[2] = { }; + const char *ptype; + + ptype = get_pd_product_type(dev); + if (!ptype) + return; + + sysfs_notify(&dev->kobj, NULL, "type"); + + envp[0] = kasprintf(GFP_KERNEL, "PRODUCT_TYPE=%s", ptype); + if (!envp[0]) + return; + + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); + kfree(envp[0]); +} + static void typec_report_identity(struct device *dev) { sysfs_notify(&dev->kobj, "identity", "id_header"); @@ -179,8 +248,22 @@ static void typec_report_identity(struct device *dev) sysfs_notify(&dev->kobj, "identity", "product_type_vdo1"); sysfs_notify(&dev->kobj, "identity", "product_type_vdo2"); sysfs_notify(&dev->kobj, "identity", "product_type_vdo3"); + typec_product_type_notify(dev); } +static ssize_t +type_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + const char *ptype; + + ptype = get_pd_product_type(dev); + if (!ptype) + return 0; + + return sysfs_emit(buf, "%s\n", ptype); +} +static DEVICE_ATTR_RO(type); + /* ------------------------------------------------------------------------- */ /* Alternate Modes */ @@ -592,6 +675,7 @@ static struct attribute *typec_partner_attrs[] = { &dev_attr_accessory_mode.attr, &dev_attr_supports_usb_power_delivery.attr, &dev_attr_number_of_alternate_modes.attr, + &dev_attr_type.attr, NULL }; @@ -604,6 +688,10 @@ static umode_t typec_partner_attr_is_visible(struct kobject *kobj, struct attrib return 0; } + if (attr == &dev_attr_type.attr) + if (!get_pd_product_type(kobj_to_dev(kobj))) + return 0; + return attr->mode; } @@ -914,15 +1002,6 @@ EXPORT_SYMBOL_GPL(typec_unregister_plug); /* Type-C Cables */ -static ssize_t -type_show(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct typec_cable *cable = to_typec_cable(dev); - - return sprintf(buf, "%s\n", cable->active ? "active" : "passive"); -} -static DEVICE_ATTR_RO(type); - static const char * const typec_plug_types[] = { [USB_PLUG_NONE] = "unknown", [USB_PLUG_TYPE_A] = "type-a", @@ -1522,6 +1601,11 @@ const struct device_type typec_port_dev_type = { /* --------------------------------------- */ /* Driver callbacks to report role updates */ +static int partner_match(struct device *dev, void *data) +{ + return is_typec_partner(dev); +} + /** * typec_set_data_role - Report data role change * @port: The USB Type-C Port where the role was changed @@ -1531,12 +1615,23 @@ const struct device_type typec_port_dev_type = { */ void typec_set_data_role(struct typec_port *port, enum typec_data_role role) { + struct device *partner_dev; + if (port->data_role == role) return; port->data_role = role; sysfs_notify(&port->dev.kobj, NULL, "data_role"); kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); + + partner_dev = device_find_child(&port->dev, NULL, partner_match); + if (!partner_dev) + return; + + if (to_typec_partner(partner_dev)->identity) + typec_product_type_notify(partner_dev); + + put_device(partner_dev); } EXPORT_SYMBOL_GPL(typec_set_data_role); @@ -1577,11 +1672,6 @@ void typec_set_vconn_role(struct typec_port *port, enum typec_role role) } EXPORT_SYMBOL_GPL(typec_set_vconn_role); -static int partner_match(struct device *dev, void *data) -{ - return is_typec_partner(dev); -} - /** * typec_set_pwr_opmode - Report changed power operation mode * @port: The USB Type-C Port where the mode was changed From 45ef561abcdd6cbaba0ab391b60d1831d2ac47af Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 3 Nov 2020 13:59:31 +0200 Subject: [PATCH 089/172] thunderbolt: Move max_boot_acl field to correct place in struct icm This makes the kernel-doc to match the ordering and also this is better place for it, not between upstream_port and vnd_cap that are used together. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/icm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index b51fc3f62b1f..03e86817afc7 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -79,9 +79,9 @@ struct icm { struct mutex request_lock; struct delayed_work rescan_work; struct pci_dev *upstream_port; - size_t max_boot_acl; int vnd_cap; bool safe_mode; + size_t max_boot_acl; bool rpm; bool can_upgrade_nvm; bool veto; From e0258805d71b9e9febeae9d9ae39ae7997384b16 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 10 Nov 2020 11:02:31 +0300 Subject: [PATCH 090/172] thunderbolt: Log which connection manager implementation is used This makes it easier to figure out whether the driver is using firmware or software based connection manager implementation. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/icm.c | 2 ++ drivers/thunderbolt/tb.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 03e86817afc7..beee6e6b8b6e 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -2302,5 +2302,7 @@ struct tb *icm_probe(struct tb_nhi *nhi) return NULL; } + tb_dbg(tb, "using firmware connection manager\n"); + return tb; } diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 214fbc92c1b7..51d5b031cada 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -1534,5 +1534,7 @@ struct tb *tb_probe(struct tb_nhi *nhi) INIT_LIST_HEAD(&tcm->dp_resources); INIT_DELAYED_WORK(&tcm->remove_work, tb_remove_work); + tb_dbg(tb, "using software connection manager\n"); + return tb; } From a3595258970bf2dfd21ba8fa3fb3d07000ae989c Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 17 Nov 2020 13:41:21 +0300 Subject: [PATCH 091/172] thunderbolt: Log adapter numbers in decimal in path activation/deactivation This makes it consistent with other debug logs that already are using decimal number for adapters (ports). Signed-off-by: Mika Westerberg --- drivers/thunderbolt/path.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c index 7c2c45d9ba4a..ca7d738d66de 100644 --- a/drivers/thunderbolt/path.c +++ b/drivers/thunderbolt/path.c @@ -454,7 +454,7 @@ void tb_path_deactivate(struct tb_path *path) return; } tb_dbg(path->tb, - "deactivating %s path from %llx:%x to %llx:%x\n", + "deactivating %s path from %llx:%u to %llx:%u\n", path->name, tb_route(path->hops[0].in_port->sw), path->hops[0].in_port->port, tb_route(path->hops[path->path_length - 1].out_port->sw), @@ -482,7 +482,7 @@ int tb_path_activate(struct tb_path *path) } tb_dbg(path->tb, - "activating %s path from %llx:%x to %llx:%x\n", + "activating %s path from %llx:%u to %llx:%u\n", path->name, tb_route(path->hops[0].in_port->sw), path->hops[0].in_port->port, tb_route(path->hops[path->path_length - 1].out_port->sw), From b658eb9d9075aa2b44834962a1efc4bc78e9bed8 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 10 Nov 2020 11:04:31 +0300 Subject: [PATCH 092/172] thunderbolt: Keep the parent runtime resumed for a while on device disconnect When doing device firmware upgrade the device will disconnect for a while and then reconnect back. Keep the parent device (and the whole domain) powered for a while so we don't need to runtime resume immediately when the device is connected back after the device upgrade completes. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/icm.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index beee6e6b8b6e..635b949fb1d6 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -870,7 +870,13 @@ icm_fr_device_disconnected(struct tb *tb, const struct icm_pkg_header *hdr) return; } + pm_runtime_get_sync(sw->dev.parent); + remove_switch(sw); + + pm_runtime_mark_last_busy(sw->dev.parent); + pm_runtime_put_autosuspend(sw->dev.parent); + tb_switch_put(sw); } @@ -1280,8 +1286,13 @@ icm_tr_device_disconnected(struct tb *tb, const struct icm_pkg_header *hdr) tb_warn(tb, "no switch exists at %llx, ignoring\n", route); return; } + pm_runtime_get_sync(sw->dev.parent); remove_switch(sw); + + pm_runtime_mark_last_busy(sw->dev.parent); + pm_runtime_put_autosuspend(sw->dev.parent); + tb_switch_put(sw); } From 463e48fa544826898791085508459de246fc4c09 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 17 Nov 2020 13:19:41 +0300 Subject: [PATCH 093/172] thunderbolt: Return -ENOTCONN when ERR_CONN is received This allows the calling code to distinguish if the error was due to ERR_CONN (adapter is disconneced or disabled) or something else. Will be needed in USB4 router NVM update in the following patch. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/ctl.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index 1d86e27a0ef3..bac08b820015 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -962,6 +962,9 @@ static int tb_cfg_get_error(struct tb_ctl *ctl, enum tb_cfg_space space, if (res->tb_error == TB_CFG_ERROR_LOCK) return -EACCES; + else if (res->tb_error == TB_CFG_ERROR_PORT_NOT_CONNECTED) + return -ENOTCONN; + return -EIO; } From 661b19473bf3ac0924560f0cbf84c15458b3c8de Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 10 Nov 2020 11:34:07 +0300 Subject: [PATCH 094/172] thunderbolt: Perform USB4 router NVM upgrade in two phases The currect code expects that the router returns back the status of the NVM authentication immediately. When tested against a real USB4 device what happens is that the router is reset and only after that the result is updated in the ROUTER_CS_26 register status field. This also seems to align better what the spec suggests. For this reason do the same what we already do with the Thunderbolt 3 devices and perform the NVM upgrade in two phases. First start the NVM_AUTH router operation and once the router is added back after the reset read the status in ROUTER_CS_26 and expose it to the userspace accordingly. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 20 ++++++++-- drivers/thunderbolt/tb.h | 1 + drivers/thunderbolt/tb_regs.h | 1 + drivers/thunderbolt/usb4.c | 75 +++++++++++++++++++++++++++-------- 4 files changed, 77 insertions(+), 20 deletions(-) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index cdfd8cccfe19..a8572f49d3ad 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -2160,6 +2160,7 @@ static int tb_switch_add_dma_port(struct tb_switch *sw) fallthrough; case 3: + case 4: ret = tb_switch_set_uuid(sw); if (ret) return ret; @@ -2175,6 +2176,22 @@ static int tb_switch_add_dma_port(struct tb_switch *sw) break; } + if (sw->no_nvm_upgrade) + return 0; + + if (tb_switch_is_usb4(sw)) { + ret = usb4_switch_nvm_authenticate_status(sw, &status); + if (ret) + return ret; + + if (status) { + tb_sw_info(sw, "switch flash authentication failed\n"); + nvm_set_auth_status(sw, status); + } + + return 0; + } + /* Root switch DMA port requires running firmware */ if (!tb_route(sw) && !tb_switch_is_icm(sw)) return 0; @@ -2183,9 +2200,6 @@ static int tb_switch_add_dma_port(struct tb_switch *sw) if (!sw->dma_port) return 0; - if (sw->no_nvm_upgrade) - return 0; - /* * If there is status already set then authentication failed * when the dma_port_flash_update_auth() returned. Power cycling diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index a21000649009..3885f2515aae 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -972,6 +972,7 @@ int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf, int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address, const void *buf, size_t size); int usb4_switch_nvm_authenticate(struct tb_switch *sw); +int usb4_switch_nvm_authenticate_status(struct tb_switch *sw, u32 *status); bool usb4_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in); int usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in); int usb4_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in); diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index e7d9529822fa..67cb173a2f8e 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -211,6 +211,7 @@ struct tb_regs_switch_header { #define ROUTER_CS_9 0x09 #define ROUTER_CS_25 0x19 #define ROUTER_CS_26 0x1a +#define ROUTER_CS_26_OPCODE_MASK GENMASK(15, 0) #define ROUTER_CS_26_STATUS_MASK GENMASK(29, 24) #define ROUTER_CS_26_STATUS_SHIFT 24 #define ROUTER_CS_26_ONS BIT(30) diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 40f13579a3fe..d88e28eee975 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -192,7 +192,9 @@ static int usb4_switch_op(struct tb_switch *sw, u16 opcode, u8 *status) if (val & ROUTER_CS_26_ONS) return -EOPNOTSUPP; - *status = (val & ROUTER_CS_26_STATUS_MASK) >> ROUTER_CS_26_STATUS_SHIFT; + if (status) + *status = (val & ROUTER_CS_26_STATUS_MASK) >> + ROUTER_CS_26_STATUS_SHIFT; return 0; } @@ -634,32 +636,71 @@ int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address, * @sw: USB4 router * * After the new NVM has been written via usb4_switch_nvm_write(), this - * function triggers NVM authentication process. If the authentication - * is successful the router is power cycled and the new NVM starts + * function triggers NVM authentication process. The router gets power + * cycled and if the authentication is successful the new NVM starts * running. In case of failure returns negative errno. + * + * The caller should call usb4_switch_nvm_authenticate_status() to read + * the status of the authentication after power cycle. It should be the + * first router operation to avoid the status being lost. */ int usb4_switch_nvm_authenticate(struct tb_switch *sw) { - u8 status = 0; int ret; - ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_AUTH, &status); + ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_AUTH, NULL); + switch (ret) { + /* + * The router is power cycled once NVM_AUTH is started so it is + * expected to get any of the following errors back. + */ + case -EACCES: + case -ENOTCONN: + case -ETIMEDOUT: + return 0; + + default: + return ret; + } +} + +/** + * usb4_switch_nvm_authenticate_status() - Read status of last NVM authenticate + * @sw: USB4 router + * @status: Status code of the operation + * + * The function checks if there is status available from the last NVM + * authenticate router operation. If there is status then %0 is returned + * and the status code is placed in @status. Returns negative errno in case + * of failure. + * + * Must be called before any other router operation. + */ +int usb4_switch_nvm_authenticate_status(struct tb_switch *sw, u32 *status) +{ + u16 opcode; + u32 val; + int ret; + + ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_26, 1); if (ret) return ret; - switch (status) { - case 0x0: - tb_sw_dbg(sw, "NVM authentication successful\n"); - return 0; - case 0x1: - return -EINVAL; - case 0x2: - return -EAGAIN; - case 0x3: - return -EOPNOTSUPP; - default: - return -EIO; + /* Check that the opcode is correct */ + opcode = val & ROUTER_CS_26_OPCODE_MASK; + if (opcode == USB4_SWITCH_OP_NVM_AUTH) { + if (val & ROUTER_CS_26_OV) + return -EBUSY; + if (val & ROUTER_CS_26_ONS) + return -EOPNOTSUPP; + + *status = (val & ROUTER_CS_26_STATUS_MASK) >> + ROUTER_CS_26_STATUS_SHIFT; + } else { + *status = 0; } + + return 0; } /** From fe265a06319bfa27cfbccd3305d93b33b78f48f2 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 2 Nov 2020 14:54:23 +0200 Subject: [PATCH 095/172] thunderbolt: Pass metadata directly to usb4_switch_op() We are going to make usb4_switch_op() to match better the corresponding firmware (ICM) USB4 router operation proxy interface, so that we can use either based on the connection manager implementation. For this reason pass metadata directly to usb4_switch_op(). Signed-off-by: Mika Westerberg --- drivers/thunderbolt/usb4.c | 77 ++++++++++++++------------------------ 1 file changed, 28 insertions(+), 49 deletions(-) diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index d88e28eee975..5f3237d66987 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -92,16 +92,6 @@ static int usb4_switch_op_write_data(struct tb_switch *sw, const void *data, return tb_sw_write(sw, data, TB_CFG_SWITCH, ROUTER_CS_9, dwords); } -static int usb4_switch_op_read_metadata(struct tb_switch *sw, u32 *metadata) -{ - return tb_sw_read(sw, metadata, TB_CFG_SWITCH, ROUTER_CS_25, 1); -} - -static int usb4_switch_op_write_metadata(struct tb_switch *sw, u32 metadata) -{ - return tb_sw_write(sw, &metadata, TB_CFG_SWITCH, ROUTER_CS_25, 1); -} - static int usb4_do_read_data(u16 address, void *buf, size_t size, read_block_fn read_block, void *read_block_data) { @@ -171,11 +161,18 @@ static int usb4_do_write_data(unsigned int address, const void *buf, size_t size return 0; } -static int usb4_switch_op(struct tb_switch *sw, u16 opcode, u8 *status) +static int usb4_switch_op(struct tb_switch *sw, u16 opcode, u32 *metadata, + u8 *status) { u32 val; int ret; + if (metadata) { + ret = tb_sw_write(sw, metadata, TB_CFG_SWITCH, ROUTER_CS_25, 1); + if (ret) + return ret; + } + val = opcode | ROUTER_CS_26_OV; ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_26, 1); if (ret) @@ -195,7 +192,9 @@ static int usb4_switch_op(struct tb_switch *sw, u16 opcode, u8 *status) if (status) *status = (val & ROUTER_CS_26_STATUS_MASK) >> ROUTER_CS_26_STATUS_SHIFT; - return 0; + + return metadata ? + tb_sw_read(sw, metadata, TB_CFG_SWITCH, ROUTER_CS_25, 1) : 0; } static void usb4_switch_check_wakes(struct tb_switch *sw) @@ -350,11 +349,7 @@ static int usb4_switch_drom_read_block(void *data, metadata |= (dwaddress << USB4_DROM_ADDRESS_SHIFT) & USB4_DROM_ADDRESS_MASK; - ret = usb4_switch_op_write_metadata(sw, metadata); - if (ret) - return ret; - - ret = usb4_switch_op(sw, USB4_SWITCH_OP_DROM_READ, &status); + ret = usb4_switch_op(sw, USB4_SWITCH_OP_DROM_READ, &metadata, &status); if (ret) return ret; @@ -510,17 +505,14 @@ int usb4_switch_nvm_sector_size(struct tb_switch *sw) u8 status; int ret; - ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_SECTOR_SIZE, &status); + ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_SECTOR_SIZE, &metadata, + &status); if (ret) return ret; if (status) return status == 0x2 ? -EOPNOTSUPP : -EIO; - ret = usb4_switch_op_read_metadata(sw, &metadata); - if (ret) - return ret; - return metadata & USB4_NVM_SECTOR_SIZE_MASK; } @@ -537,11 +529,7 @@ static int usb4_switch_nvm_read_block(void *data, metadata |= (dwaddress << USB4_NVM_READ_OFFSET_SHIFT) & USB4_NVM_READ_OFFSET_MASK; - ret = usb4_switch_op_write_metadata(sw, metadata); - if (ret) - return ret; - - ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_READ, &status); + ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_READ, &metadata, &status); if (ret) return ret; @@ -579,11 +567,8 @@ static int usb4_switch_nvm_set_offset(struct tb_switch *sw, metadata = (dwaddress << USB4_NVM_SET_OFFSET_SHIFT) & USB4_NVM_SET_OFFSET_MASK; - ret = usb4_switch_op_write_metadata(sw, metadata); - if (ret) - return ret; - - ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_SET_OFFSET, &status); + ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_SET_OFFSET, &metadata, + &status); if (ret) return ret; @@ -601,7 +586,7 @@ static int usb4_switch_nvm_write_next_block(void *data, const void *buf, if (ret) return ret; - ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_WRITE, &status); + ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_WRITE, NULL, &status); if (ret) return ret; @@ -648,7 +633,7 @@ int usb4_switch_nvm_authenticate(struct tb_switch *sw) { int ret; - ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_AUTH, NULL); + ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_AUTH, NULL, NULL); switch (ret) { /* * The router is power cycled once NVM_AUTH is started so it is @@ -714,14 +699,12 @@ int usb4_switch_nvm_authenticate_status(struct tb_switch *sw, u32 *status) */ bool usb4_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in) { + u32 metadata = in->port; u8 status; int ret; - ret = usb4_switch_op_write_metadata(sw, in->port); - if (ret) - return false; - - ret = usb4_switch_op(sw, USB4_SWITCH_OP_QUERY_DP_RESOURCE, &status); + ret = usb4_switch_op(sw, USB4_SWITCH_OP_QUERY_DP_RESOURCE, &metadata, + &status); /* * If DP resource allocation is not supported assume it is * always available. @@ -746,14 +729,12 @@ bool usb4_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in) */ int usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in) { + u32 metadata = in->port; u8 status; int ret; - ret = usb4_switch_op_write_metadata(sw, in->port); - if (ret) - return ret; - - ret = usb4_switch_op(sw, USB4_SWITCH_OP_ALLOC_DP_RESOURCE, &status); + ret = usb4_switch_op(sw, USB4_SWITCH_OP_ALLOC_DP_RESOURCE, &metadata, + &status); if (ret == -EOPNOTSUPP) return 0; else if (ret) @@ -771,14 +752,12 @@ int usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in) */ int usb4_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in) { + u32 metadata = in->port; u8 status; int ret; - ret = usb4_switch_op_write_metadata(sw, in->port); - if (ret) - return ret; - - ret = usb4_switch_op(sw, USB4_SWITCH_OP_DEALLOC_DP_RESOURCE, &status); + ret = usb4_switch_op(sw, USB4_SWITCH_OP_DEALLOC_DP_RESOURCE, &metadata, + &status); if (ret == -EOPNOTSUPP) return 0; else if (ret) From 83bab44ada0512b054844e661279d68d0c8f3d03 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Mon, 2 Nov 2020 15:09:44 +0200 Subject: [PATCH 096/172] thunderbolt: Pass TX and RX data directly to usb4_switch_op() We are going to make usb4_switch_op() to match better the corresponding firmware (ICM) USB4 router operation proxy interface, so that we can use either based on the connection manager implementation. For this reason rename usb4_switch_op() to __usb4_switch_op() that provides the most complete interface. Then make usb4_switch_op() and usb4_switch_op_data() call it with correct set of parameters and update the callers accordingly. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/usb4.c | 85 +++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index 5f3237d66987..c1bb5ec6e1db 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -74,24 +74,6 @@ static int usb4_switch_wait_for_bit(struct tb_switch *sw, u32 offset, u32 bit, return -ETIMEDOUT; } -static int usb4_switch_op_read_data(struct tb_switch *sw, void *data, - size_t dwords) -{ - if (dwords > USB4_DATA_DWORDS) - return -EINVAL; - - return tb_sw_read(sw, data, TB_CFG_SWITCH, ROUTER_CS_9, dwords); -} - -static int usb4_switch_op_write_data(struct tb_switch *sw, const void *data, - size_t dwords) -{ - if (dwords > USB4_DATA_DWORDS) - return -EINVAL; - - return tb_sw_write(sw, data, TB_CFG_SWITCH, ROUTER_CS_9, dwords); -} - static int usb4_do_read_data(u16 address, void *buf, size_t size, read_block_fn read_block, void *read_block_data) { @@ -161,17 +143,27 @@ static int usb4_do_write_data(unsigned int address, const void *buf, size_t size return 0; } -static int usb4_switch_op(struct tb_switch *sw, u16 opcode, u32 *metadata, - u8 *status) +static int __usb4_switch_op(struct tb_switch *sw, u16 opcode, u32 *metadata, + u8 *status, const void *tx_data, size_t tx_dwords, + void *rx_data, size_t rx_dwords) { u32 val; int ret; + if (tx_dwords > USB4_DATA_DWORDS || rx_dwords > USB4_DATA_DWORDS) + return -EINVAL; + if (metadata) { ret = tb_sw_write(sw, metadata, TB_CFG_SWITCH, ROUTER_CS_25, 1); if (ret) return ret; } + if (tx_dwords) { + ret = tb_sw_write(sw, tx_data, TB_CFG_SWITCH, ROUTER_CS_9, + tx_dwords); + if (ret) + return ret; + } val = opcode | ROUTER_CS_26_OV; ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_26, 1); @@ -193,8 +185,34 @@ static int usb4_switch_op(struct tb_switch *sw, u16 opcode, u32 *metadata, *status = (val & ROUTER_CS_26_STATUS_MASK) >> ROUTER_CS_26_STATUS_SHIFT; - return metadata ? - tb_sw_read(sw, metadata, TB_CFG_SWITCH, ROUTER_CS_25, 1) : 0; + if (metadata) { + ret = tb_sw_read(sw, metadata, TB_CFG_SWITCH, ROUTER_CS_25, 1); + if (ret) + return ret; + } + if (rx_dwords) { + ret = tb_sw_read(sw, rx_data, TB_CFG_SWITCH, ROUTER_CS_9, + rx_dwords); + if (ret) + return ret; + } + + return 0; +} + +static inline int usb4_switch_op(struct tb_switch *sw, u16 opcode, + u32 *metadata, u8 *status) +{ + return __usb4_switch_op(sw, opcode, metadata, status, NULL, 0, NULL, 0); +} + +static inline int usb4_switch_op_data(struct tb_switch *sw, u16 opcode, + u32 *metadata, u8 *status, + const void *tx_data, size_t tx_dwords, + void *rx_data, size_t rx_dwords) +{ + return __usb4_switch_op(sw, opcode, metadata, status, tx_data, + tx_dwords, rx_data, rx_dwords); } static void usb4_switch_check_wakes(struct tb_switch *sw) @@ -349,14 +367,12 @@ static int usb4_switch_drom_read_block(void *data, metadata |= (dwaddress << USB4_DROM_ADDRESS_SHIFT) & USB4_DROM_ADDRESS_MASK; - ret = usb4_switch_op(sw, USB4_SWITCH_OP_DROM_READ, &metadata, &status); + ret = usb4_switch_op_data(sw, USB4_SWITCH_OP_DROM_READ, &metadata, + &status, NULL, 0, buf, dwords); if (ret) return ret; - if (status) - return -EIO; - - return usb4_switch_op_read_data(sw, buf, dwords); + return status ? -EIO : 0; } /** @@ -529,14 +545,12 @@ static int usb4_switch_nvm_read_block(void *data, metadata |= (dwaddress << USB4_NVM_READ_OFFSET_SHIFT) & USB4_NVM_READ_OFFSET_MASK; - ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_READ, &metadata, &status); + ret = usb4_switch_op_data(sw, USB4_SWITCH_OP_NVM_READ, &metadata, + &status, NULL, 0, buf, dwords); if (ret) return ret; - if (status) - return -EIO; - - return usb4_switch_op_read_data(sw, buf, dwords); + return status ? -EIO : 0; } /** @@ -582,11 +596,8 @@ static int usb4_switch_nvm_write_next_block(void *data, const void *buf, u8 status; int ret; - ret = usb4_switch_op_write_data(sw, buf, dwords); - if (ret) - return ret; - - ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_WRITE, NULL, &status); + ret = usb4_switch_op_data(sw, USB4_SWITCH_OP_NVM_WRITE, NULL, &status, + buf, dwords, NULL, 0); if (ret) return ret; From 9490f71167feba55349e33854f5e51a1a3af9e8c Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 3 Nov 2020 13:58:00 +0200 Subject: [PATCH 097/172] thunderbolt: Add connection manager specific hooks for USB4 router operations Intel USB4 host routers that run the firmware based connection manager (ICM) may implement a proxy for USB4 router operations. This is to avoid the firmware to race with the OS driver, as both may need to run these operations. This adds two new connection manager specific callbacks which, if provided, get called instead of the native USB4 router operation. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.h | 13 ++++++++++ drivers/thunderbolt/usb4.c | 50 +++++++++++++++++++++++++++++++++----- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 3885f2515aae..d19dbc8e9457 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -367,6 +367,14 @@ struct tb_path { * @disconnect_pcie_paths: Disconnects PCIe paths before NVM update * @approve_xdomain_paths: Approve (establish) XDomain DMA paths * @disconnect_xdomain_paths: Disconnect XDomain DMA paths + * @usb4_switch_op: Optional proxy for USB4 router operations. If set + * this will be called whenever USB4 router operation is + * performed. If this returns %-EOPNOTSUPP then the + * native USB4 router operation is called. + * @usb4_switch_nvm_authenticate_status: Optional callback that the CM + * implementation can be used to + * return status of USB4 NVM_AUTH + * router operation. */ struct tb_cm_ops { int (*driver_ready)(struct tb *tb); @@ -393,6 +401,11 @@ struct tb_cm_ops { int (*disconnect_pcie_paths)(struct tb *tb); int (*approve_xdomain_paths)(struct tb *tb, struct tb_xdomain *xd); int (*disconnect_xdomain_paths)(struct tb *tb, struct tb_xdomain *xd); + int (*usb4_switch_op)(struct tb_switch *sw, u16 opcode, u32 *metadata, + u8 *status, const void *tx_data, size_t tx_data_len, + void *rx_data, size_t rx_data_len); + int (*usb4_switch_nvm_authenticate_status)(struct tb_switch *sw, + u32 *status); }; static inline void *tb_priv(struct tb *tb) diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index c1bb5ec6e1db..cbf1c0536360 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -143,16 +143,14 @@ static int usb4_do_write_data(unsigned int address, const void *buf, size_t size return 0; } -static int __usb4_switch_op(struct tb_switch *sw, u16 opcode, u32 *metadata, - u8 *status, const void *tx_data, size_t tx_dwords, - void *rx_data, size_t rx_dwords) +static int usb4_native_switch_op(struct tb_switch *sw, u16 opcode, + u32 *metadata, u8 *status, + const void *tx_data, size_t tx_dwords, + void *rx_data, size_t rx_dwords) { u32 val; int ret; - if (tx_dwords > USB4_DATA_DWORDS || rx_dwords > USB4_DATA_DWORDS) - return -EINVAL; - if (metadata) { ret = tb_sw_write(sw, metadata, TB_CFG_SWITCH, ROUTER_CS_25, 1); if (ret) @@ -200,6 +198,39 @@ static int __usb4_switch_op(struct tb_switch *sw, u16 opcode, u32 *metadata, return 0; } +static int __usb4_switch_op(struct tb_switch *sw, u16 opcode, u32 *metadata, + u8 *status, const void *tx_data, size_t tx_dwords, + void *rx_data, size_t rx_dwords) +{ + const struct tb_cm_ops *cm_ops = sw->tb->cm_ops; + + if (tx_dwords > USB4_DATA_DWORDS || rx_dwords > USB4_DATA_DWORDS) + return -EINVAL; + + /* + * If the connection manager implementation provides USB4 router + * operation proxy callback, call it here instead of running the + * operation natively. + */ + if (cm_ops->usb4_switch_op) { + int ret; + + ret = cm_ops->usb4_switch_op(sw, opcode, metadata, status, + tx_data, tx_dwords, rx_data, + rx_dwords); + if (ret != -EOPNOTSUPP) + return ret; + + /* + * If the proxy was not supported then run the native + * router operation instead. + */ + } + + return usb4_native_switch_op(sw, opcode, metadata, status, tx_data, + tx_dwords, rx_data, rx_dwords); +} + static inline int usb4_switch_op(struct tb_switch *sw, u16 opcode, u32 *metadata, u8 *status) { @@ -674,10 +705,17 @@ int usb4_switch_nvm_authenticate(struct tb_switch *sw) */ int usb4_switch_nvm_authenticate_status(struct tb_switch *sw, u32 *status) { + const struct tb_cm_ops *cm_ops = sw->tb->cm_ops; u16 opcode; u32 val; int ret; + if (cm_ops->usb4_switch_nvm_authenticate_status) { + ret = cm_ops->usb4_switch_nvm_authenticate_status(sw, status); + if (ret != -EOPNOTSUPP) + return ret; + } + ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_26, 1); if (ret) return ret; From 579f14217c952975e7d11e300c669af0c47bfe04 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 12 Nov 2020 15:45:18 +0200 Subject: [PATCH 098/172] thunderbolt: Move constants for USB4 router operations to tb_regs.h We are going to use these in subsequent patch so make them available outside of usb4.c. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb_regs.h | 13 +++++++++++++ drivers/thunderbolt/usb4.c | 12 ------------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index 67cb173a2f8e..ae427a953489 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -217,6 +217,19 @@ struct tb_regs_switch_header { #define ROUTER_CS_26_ONS BIT(30) #define ROUTER_CS_26_OV BIT(31) +/* USB4 router operations opcodes */ +enum usb4_switch_op { + USB4_SWITCH_OP_QUERY_DP_RESOURCE = 0x10, + USB4_SWITCH_OP_ALLOC_DP_RESOURCE = 0x11, + USB4_SWITCH_OP_DEALLOC_DP_RESOURCE = 0x12, + USB4_SWITCH_OP_NVM_WRITE = 0x20, + USB4_SWITCH_OP_NVM_AUTH = 0x21, + USB4_SWITCH_OP_NVM_READ = 0x22, + USB4_SWITCH_OP_NVM_SET_OFFSET = 0x23, + USB4_SWITCH_OP_DROM_READ = 0x24, + USB4_SWITCH_OP_NVM_SECTOR_SIZE = 0x25, +}; + /* Router TMU configuration */ #define TMU_RTR_CS_0 0x00 #define TMU_RTR_CS_0_TD BIT(27) diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c index cbf1c0536360..6a0aa83a1ac8 100644 --- a/drivers/thunderbolt/usb4.c +++ b/drivers/thunderbolt/usb4.c @@ -16,18 +16,6 @@ #define USB4_DATA_DWORDS 16 #define USB4_DATA_RETRIES 3 -enum usb4_switch_op { - USB4_SWITCH_OP_QUERY_DP_RESOURCE = 0x10, - USB4_SWITCH_OP_ALLOC_DP_RESOURCE = 0x11, - USB4_SWITCH_OP_DEALLOC_DP_RESOURCE = 0x12, - USB4_SWITCH_OP_NVM_WRITE = 0x20, - USB4_SWITCH_OP_NVM_AUTH = 0x21, - USB4_SWITCH_OP_NVM_READ = 0x22, - USB4_SWITCH_OP_NVM_SET_OFFSET = 0x23, - USB4_SWITCH_OP_DROM_READ = 0x24, - USB4_SWITCH_OP_NVM_SECTOR_SIZE = 0x25, -}; - enum usb4_sb_target { USB4_SB_TARGET_ROUTER, USB4_SB_TARGET_PARTNER, From 9039387e166edab35c89ddcc057529e332cc4089 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 3 Nov 2020 13:55:32 +0200 Subject: [PATCH 099/172] thunderbolt: Add USB4 router operation proxy for firmware connection manager Intel Maple Ridge and Tiger Lake connection manager firmware implements a USB4 router operation proxy that should be used instead of direct register access to avoid races with the firmware. This is supported in all firmwares where the protocol version field returned in the driver ready response is 3 (or higher). This adds the USB4 router proxy operations support to the driver so that we first check the protocol version and if it is 3 (or higher) the USB4 router operation is run through the firmware provided proxy. Otherwise the native version is used. Most USB4 router proxy operations are pretty straightforward except NVM_AUTH where the firmware only responds once the router is restarted but before it sends device connected notification. To support this we split the operation so that the reply is received asynchronously and stored to struct icm. This last reply is then returned in icm_usb4_switch_nvm_authenticate_status() if available. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/icm.c | 214 ++++++++++++++++++++++++++++++++-- drivers/thunderbolt/tb_msgs.h | 28 +++++ 2 files changed, 232 insertions(+), 10 deletions(-) diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 635b949fb1d6..35935c106e3d 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -48,6 +48,18 @@ static bool start_icm; module_param(start_icm, bool, 0444); MODULE_PARM_DESC(start_icm, "start ICM firmware if it is not running (default: false)"); +/** + * struct usb4_switch_nvm_auth - Holds USB4 NVM_AUTH status + * @reply: Reply from ICM firmware is placed here + * @request: Request that is sent to ICM firmware + * @icm: Pointer to ICM private data + */ +struct usb4_switch_nvm_auth { + struct icm_usb4_switch_op_response reply; + struct icm_usb4_switch_op request; + struct icm *icm; +}; + /** * struct icm - Internal connection manager private data * @request_lock: Makes sure only one message is send to ICM at time @@ -61,6 +73,8 @@ MODULE_PARM_DESC(start_icm, "start ICM firmware if it is not running (default: f * @max_boot_acl: Maximum number of preboot ACL entries (%0 if not supported) * @rpm: Does the controller support runtime PM (RTD3) * @can_upgrade_nvm: Can the NVM firmware be upgrade on this controller + * @proto_version: Firmware protocol version + * @last_nvm_auth: Last USB4 router NVM_AUTH result (or %NULL if not set) * @veto: Is RTD3 veto in effect * @is_supported: Checks if we can support ICM on this controller * @cio_reset: Trigger CIO reset @@ -84,6 +98,8 @@ struct icm { size_t max_boot_acl; bool rpm; bool can_upgrade_nvm; + u8 proto_version; + struct usb4_switch_nvm_auth *last_nvm_auth; bool veto; bool (*is_supported)(struct tb *tb); int (*cio_reset)(struct tb *tb); @@ -92,7 +108,7 @@ struct icm { void (*save_devices)(struct tb *tb); int (*driver_ready)(struct tb *tb, enum tb_security_level *security_level, - size_t *nboot_acl, bool *rpm); + u8 *proto_version, size_t *nboot_acl, bool *rpm); void (*set_uuid)(struct tb *tb); void (*device_connected)(struct tb *tb, const struct icm_pkg_header *hdr); @@ -437,7 +453,7 @@ static void icm_fr_save_devices(struct tb *tb) static int icm_fr_driver_ready(struct tb *tb, enum tb_security_level *security_level, - size_t *nboot_acl, bool *rpm) + u8 *proto_version, size_t *nboot_acl, bool *rpm) { struct icm_fr_pkg_driver_ready_response reply; struct icm_pkg_driver_ready request = { @@ -992,7 +1008,7 @@ static int icm_tr_cio_reset(struct tb *tb) static int icm_tr_driver_ready(struct tb *tb, enum tb_security_level *security_level, - size_t *nboot_acl, bool *rpm) + u8 *proto_version, size_t *nboot_acl, bool *rpm) { struct icm_tr_pkg_driver_ready_response reply; struct icm_pkg_driver_ready request = { @@ -1008,6 +1024,9 @@ icm_tr_driver_ready(struct tb *tb, enum tb_security_level *security_level, if (security_level) *security_level = reply.info & ICM_TR_INFO_SLEVEL_MASK; + if (proto_version) + *proto_version = (reply.info & ICM_TR_INFO_PROTO_VERSION_MASK) >> + ICM_TR_INFO_PROTO_VERSION_SHIFT; if (nboot_acl) *nboot_acl = (reply.info & ICM_TR_INFO_BOOT_ACL_MASK) >> ICM_TR_INFO_BOOT_ACL_SHIFT; @@ -1461,7 +1480,7 @@ static int icm_ar_get_mode(struct tb *tb) static int icm_ar_driver_ready(struct tb *tb, enum tb_security_level *security_level, - size_t *nboot_acl, bool *rpm) + u8 *proto_version, size_t *nboot_acl, bool *rpm) { struct icm_ar_pkg_driver_ready_response reply; struct icm_pkg_driver_ready request = { @@ -1591,7 +1610,7 @@ static int icm_ar_set_boot_acl(struct tb *tb, const uuid_t *uuids, static int icm_icl_driver_ready(struct tb *tb, enum tb_security_level *security_level, - size_t *nboot_acl, bool *rpm) + u8 *proto_version, size_t *nboot_acl, bool *rpm) { struct icm_tr_pkg_driver_ready_response reply; struct icm_pkg_driver_ready request = { @@ -1605,6 +1624,10 @@ icm_icl_driver_ready(struct tb *tb, enum tb_security_level *security_level, if (ret) return ret; + if (proto_version) + *proto_version = (reply.info & ICM_TR_INFO_PROTO_VERSION_MASK) >> + ICM_TR_INFO_PROTO_VERSION_SHIFT; + /* Ice Lake always supports RTD3 */ if (rpm) *rpm = true; @@ -1713,13 +1736,14 @@ static void icm_handle_event(struct tb *tb, enum tb_cfg_pkg_type type, static int __icm_driver_ready(struct tb *tb, enum tb_security_level *security_level, - size_t *nboot_acl, bool *rpm) + u8 *proto_version, size_t *nboot_acl, bool *rpm) { struct icm *icm = tb_priv(tb); unsigned int retries = 50; int ret; - ret = icm->driver_ready(tb, security_level, nboot_acl, rpm); + ret = icm->driver_ready(tb, security_level, proto_version, nboot_acl, + rpm); if (ret) { tb_err(tb, "failed to send driver ready to ICM\n"); return ret; @@ -1929,8 +1953,8 @@ static int icm_driver_ready(struct tb *tb) return 0; } - ret = __icm_driver_ready(tb, &tb->security_level, &tb->nboot_acl, - &icm->rpm); + ret = __icm_driver_ready(tb, &tb->security_level, &icm->proto_version, + &tb->nboot_acl, &icm->rpm); if (ret) return ret; @@ -1941,6 +1965,9 @@ static int icm_driver_ready(struct tb *tb) if (tb->nboot_acl > icm->max_boot_acl) tb->nboot_acl = 0; + if (icm->proto_version >= 3) + tb_dbg(tb, "USB4 proxy operations supported\n"); + return 0; } @@ -2052,7 +2079,7 @@ static void icm_complete(struct tb *tb) * Now all existing children should be resumed, start events * from ICM to get updated status. */ - __icm_driver_ready(tb, NULL, NULL, NULL); + __icm_driver_ready(tb, NULL, NULL, NULL, NULL); /* * We do not get notifications of devices that have been @@ -2131,6 +2158,8 @@ static void icm_stop(struct tb *tb) tb_switch_remove(tb->root_switch); tb->root_switch = NULL; nhi_mailbox_cmd(tb->nhi, NHI_MAILBOX_DRV_UNLOADS, 0); + kfree(icm->last_nvm_auth); + icm->last_nvm_auth = NULL; } static int icm_disconnect_pcie_paths(struct tb *tb) @@ -2138,6 +2167,165 @@ static int icm_disconnect_pcie_paths(struct tb *tb) return nhi_mailbox_cmd(tb->nhi, NHI_MAILBOX_DISCONNECT_PCIE_PATHS, 0); } +static void icm_usb4_switch_nvm_auth_complete(void *data) +{ + struct usb4_switch_nvm_auth *auth = data; + struct icm *icm = auth->icm; + struct tb *tb = icm_to_tb(icm); + + tb_dbg(tb, "NVM_AUTH response for %llx flags %#x status %#x\n", + get_route(auth->reply.route_hi, auth->reply.route_lo), + auth->reply.hdr.flags, auth->reply.status); + + mutex_lock(&tb->lock); + if (WARN_ON(icm->last_nvm_auth)) + kfree(icm->last_nvm_auth); + icm->last_nvm_auth = auth; + mutex_unlock(&tb->lock); +} + +static int icm_usb4_switch_nvm_authenticate(struct tb *tb, u64 route) +{ + struct usb4_switch_nvm_auth *auth; + struct icm *icm = tb_priv(tb); + struct tb_cfg_request *req; + int ret; + + auth = kzalloc(sizeof(*auth), GFP_KERNEL); + if (!auth) + return -ENOMEM; + + auth->icm = icm; + auth->request.hdr.code = ICM_USB4_SWITCH_OP; + auth->request.route_hi = upper_32_bits(route); + auth->request.route_lo = lower_32_bits(route); + auth->request.opcode = USB4_SWITCH_OP_NVM_AUTH; + + req = tb_cfg_request_alloc(); + if (!req) { + ret = -ENOMEM; + goto err_free_auth; + } + + req->match = icm_match; + req->copy = icm_copy; + req->request = &auth->request; + req->request_size = sizeof(auth->request); + req->request_type = TB_CFG_PKG_ICM_CMD; + req->response = &auth->reply; + req->npackets = 1; + req->response_size = sizeof(auth->reply); + req->response_type = TB_CFG_PKG_ICM_RESP; + + tb_dbg(tb, "NVM_AUTH request for %llx\n", route); + + mutex_lock(&icm->request_lock); + ret = tb_cfg_request(tb->ctl, req, icm_usb4_switch_nvm_auth_complete, + auth); + mutex_unlock(&icm->request_lock); + + tb_cfg_request_put(req); + if (ret) + goto err_free_auth; + return 0; + +err_free_auth: + kfree(auth); + return ret; +} + +static int icm_usb4_switch_op(struct tb_switch *sw, u16 opcode, u32 *metadata, + u8 *status, const void *tx_data, size_t tx_data_len, + void *rx_data, size_t rx_data_len) +{ + struct icm_usb4_switch_op_response reply; + struct icm_usb4_switch_op request; + struct tb *tb = sw->tb; + struct icm *icm = tb_priv(tb); + u64 route = tb_route(sw); + int ret; + + /* + * USB4 router operation proxy is supported in firmware if the + * protocol version is 3 or higher. + */ + if (icm->proto_version < 3) + return -EOPNOTSUPP; + + /* + * NVM_AUTH is a special USB4 proxy operation that does not + * return immediately so handle it separately. + */ + if (opcode == USB4_SWITCH_OP_NVM_AUTH) + return icm_usb4_switch_nvm_authenticate(tb, route); + + memset(&request, 0, sizeof(request)); + request.hdr.code = ICM_USB4_SWITCH_OP; + request.route_hi = upper_32_bits(route); + request.route_lo = lower_32_bits(route); + request.opcode = opcode; + if (metadata) + request.metadata = *metadata; + + if (tx_data_len) { + request.data_len_valid |= ICM_USB4_SWITCH_DATA_VALID; + if (tx_data_len < ARRAY_SIZE(request.data)) + request.data_len_valid = + tx_data_len & ICM_USB4_SWITCH_DATA_LEN_MASK; + memcpy(request.data, tx_data, tx_data_len * sizeof(u32)); + } + + memset(&reply, 0, sizeof(reply)); + ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply), + 1, ICM_TIMEOUT); + if (ret) + return ret; + + if (reply.hdr.flags & ICM_FLAGS_ERROR) + return -EIO; + + if (status) + *status = reply.status; + + if (metadata) + *metadata = reply.metadata; + + if (rx_data_len) + memcpy(rx_data, reply.data, rx_data_len * sizeof(u32)); + + return 0; +} + +static int icm_usb4_switch_nvm_authenticate_status(struct tb_switch *sw, + u32 *status) +{ + struct usb4_switch_nvm_auth *auth; + struct tb *tb = sw->tb; + struct icm *icm = tb_priv(tb); + int ret = 0; + + if (icm->proto_version < 3) + return -EOPNOTSUPP; + + auth = icm->last_nvm_auth; + icm->last_nvm_auth = NULL; + + if (auth && auth->reply.route_hi == sw->config.route_hi && + auth->reply.route_lo == sw->config.route_lo) { + tb_dbg(tb, "NVM_AUTH found for %llx flags 0x%#x status %#x\n", + tb_route(sw), auth->reply.hdr.flags, auth->reply.status); + if (auth->reply.hdr.flags & ICM_FLAGS_ERROR) + ret = -EIO; + else + *status = auth->reply.status; + } else { + *status = 0; + } + + kfree(auth); + return ret; +} + /* Falcon Ridge */ static const struct tb_cm_ops icm_fr_ops = { .driver_ready = icm_driver_ready, @@ -2196,6 +2384,9 @@ static const struct tb_cm_ops icm_tr_ops = { .disconnect_pcie_paths = icm_disconnect_pcie_paths, .approve_xdomain_paths = icm_tr_approve_xdomain_paths, .disconnect_xdomain_paths = icm_tr_disconnect_xdomain_paths, + .usb4_switch_op = icm_usb4_switch_op, + .usb4_switch_nvm_authenticate_status = + icm_usb4_switch_nvm_authenticate_status, }; /* Ice Lake */ @@ -2209,6 +2400,9 @@ static const struct tb_cm_ops icm_icl_ops = { .handle_event = icm_handle_event, .approve_xdomain_paths = icm_tr_approve_xdomain_paths, .disconnect_xdomain_paths = icm_tr_disconnect_xdomain_paths, + .usb4_switch_op = icm_usb4_switch_op, + .usb4_switch_nvm_authenticate_status = + icm_usb4_switch_nvm_authenticate_status, }; struct tb *icm_probe(struct tb_nhi *nhi) diff --git a/drivers/thunderbolt/tb_msgs.h b/drivers/thunderbolt/tb_msgs.h index 0e01dbc63e72..bcabfcb2fd03 100644 --- a/drivers/thunderbolt/tb_msgs.h +++ b/drivers/thunderbolt/tb_msgs.h @@ -106,6 +106,7 @@ enum icm_pkg_code { ICM_APPROVE_XDOMAIN = 0x10, ICM_DISCONNECT_XDOMAIN = 0x11, ICM_PREBOOT_ACL = 0x18, + ICM_USB4_SWITCH_OP = 0x20, }; enum icm_event_code { @@ -343,6 +344,8 @@ struct icm_tr_pkg_driver_ready_response { #define ICM_TR_FLAGS_RTD3 BIT(6) #define ICM_TR_INFO_SLEVEL_MASK GENMASK(2, 0) +#define ICM_TR_INFO_PROTO_VERSION_MASK GENMASK(6, 4) +#define ICM_TR_INFO_PROTO_VERSION_SHIFT 4 #define ICM_TR_INFO_BOOT_ACL_SHIFT 7 #define ICM_TR_INFO_BOOT_ACL_MASK GENMASK(12, 7) @@ -478,6 +481,31 @@ struct icm_icl_event_rtd3_veto { u32 veto_reason; }; +/* USB4 ICM messages */ + +struct icm_usb4_switch_op { + struct icm_pkg_header hdr; + u32 route_hi; + u32 route_lo; + u32 metadata; + u16 opcode; + u16 data_len_valid; + u32 data[16]; +}; + +#define ICM_USB4_SWITCH_DATA_LEN_MASK GENMASK(3, 0) +#define ICM_USB4_SWITCH_DATA_VALID BIT(4) + +struct icm_usb4_switch_op_response { + struct icm_pkg_header hdr; + u32 route_hi; + u32 route_lo; + u32 metadata; + u16 opcode; + u16 status; + u32 data[16]; +}; + /* XDomain messages */ struct tb_xdomain_header { From db0746e3399ee87ee5f957880811da16faa89fb8 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 31 Jan 2020 19:24:30 +0300 Subject: [PATCH 100/172] thunderbolt: Add support for Intel Maple Ridge Maple Ridge is first discrete USB4 host controller from Intel. It comes with firmware based connection manager and the flows are similar as used in Intel Titan Ridge. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/icm.c | 11 +++++++++++ drivers/thunderbolt/nhi.h | 1 + 2 files changed, 12 insertions(+) diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 35935c106e3d..adc7b61937a1 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -2499,6 +2499,17 @@ struct tb *icm_probe(struct tb_nhi *nhi) icm->rtd3_veto = icm_icl_rtd3_veto; tb->cm_ops = &icm_icl_ops; break; + + case PCI_DEVICE_ID_INTEL_MAPLE_RIDGE_4C_NHI: + icm->is_supported = icm_tgl_is_supported; + icm->get_mode = icm_ar_get_mode; + icm->driver_ready = icm_tr_driver_ready; + icm->device_connected = icm_tr_device_connected; + icm->device_disconnected = icm_tr_device_disconnected; + icm->xdomain_connected = icm_tr_xdomain_connected; + icm->xdomain_disconnected = icm_tr_xdomain_disconnected; + tb->cm_ops = &icm_tr_ops; + break; } if (!icm->is_supported || !icm->is_supported(tb)) { diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 80162e4b013f..ffe0531c0fd0 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -55,6 +55,7 @@ extern const struct tb_nhi_ops icl_nhi_ops; * need for the PCI quirk anymore as we will use ICM also on Apple * hardware. */ +#define PCI_DEVICE_ID_INTEL_MAPLE_RIDGE_4C_NHI 0x1137 #define PCI_DEVICE_ID_INTEL_WIN_RIDGE_2C_NHI 0x157d #define PCI_DEVICE_ID_INTEL_WIN_RIDGE_2C_BRIDGE 0x157e #define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI 0x15bf From 95168d624f3a8dee466525767200391a0fb006b9 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 16 Nov 2020 17:18:21 +0100 Subject: [PATCH 101/172] USB: serial: cp210x: return early on unchanged termios Return early from set_termios() in case no relevant terminal settings have changed. This avoids testing each parameter in turn and specifically allows the line-control handling to be cleaned up further. Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index d0c05aa8a0d6..f1fd109d97d5 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -1352,6 +1352,15 @@ static void cp210x_disable_event_mode(struct usb_serial_port *port) port_priv->event_mode = false; } +static bool cp210x_termios_change(const struct ktermios *a, const struct ktermios *b) +{ + bool iflag_change; + + iflag_change = ((a->c_iflag ^ b->c_iflag) & INPCK); + + return tty_termios_hw_change(a, b) || iflag_change; +} + static void cp210x_set_termios(struct tty_struct *tty, struct usb_serial_port *port, struct ktermios *old_termios) { @@ -1359,6 +1368,9 @@ static void cp210x_set_termios(struct tty_struct *tty, unsigned int cflag, old_cflag; u16 bits; + if (!cp210x_termios_change(&tty->termios, old_termios)) + return; + cflag = tty->termios.c_cflag; old_cflag = old_termios->c_cflag; From d42976296c3389b556913d249f7c5626b754ec26 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 16 Nov 2020 17:18:22 +0100 Subject: [PATCH 102/172] USB: serial: cp210x: clean up line-control handling Update the line-control settings in one request unconditionally instead of setting the word-length, parity and stop-bit settings separately. This avoids multiple requests when several settings are changed even if this scheme could potentially also be used to detect unsupported device settings. Since all device types but CP2101 appears to support all settings, let's handle that one specifically and also report back the unsupported settings properly through termios by clearing the corresponding bits. Also drop the related unnecessary debug printks. Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 101 +++++++++++++++--------------------- 1 file changed, 41 insertions(+), 60 deletions(-) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index f1fd109d97d5..ad134e517849 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -1364,9 +1364,11 @@ static bool cp210x_termios_change(const struct ktermios *a, const struct ktermio static void cp210x_set_termios(struct tty_struct *tty, struct usb_serial_port *port, struct ktermios *old_termios) { + struct cp210x_serial_private *priv = usb_get_serial_data(port->serial); struct device *dev = &port->dev; unsigned int cflag, old_cflag; u16 bits; + int ret; if (!cp210x_termios_change(&tty->termios, old_termios)) return; @@ -1377,74 +1379,53 @@ static void cp210x_set_termios(struct tty_struct *tty, if (tty->termios.c_ospeed != old_termios->c_ospeed) cp210x_change_speed(tty, port, old_termios); - /* If the number of data bits is to be updated */ - if ((cflag & CSIZE) != (old_cflag & CSIZE)) { - cp210x_get_line_ctl(port, &bits); - bits &= ~BITS_DATA_MASK; - switch (cflag & CSIZE) { - case CS5: - bits |= BITS_DATA_5; - dev_dbg(dev, "%s - data bits = 5\n", __func__); - break; - case CS6: - bits |= BITS_DATA_6; - dev_dbg(dev, "%s - data bits = 6\n", __func__); - break; - case CS7: - bits |= BITS_DATA_7; - dev_dbg(dev, "%s - data bits = 7\n", __func__); - break; - case CS8: - default: - bits |= BITS_DATA_8; - dev_dbg(dev, "%s - data bits = 8\n", __func__); - break; - } - if (cp210x_write_u16_reg(port, CP210X_SET_LINE_CTL, bits)) - dev_dbg(dev, "Number of data bits requested not supported by device\n"); + /* CP2101 only supports CS8, 1 stop bit and non-stick parity. */ + if (priv->partnum == CP210X_PARTNUM_CP2101) { + tty->termios.c_cflag &= ~(CSIZE | CSTOPB | CMSPAR); + tty->termios.c_cflag |= CS8; } - if ((cflag & (PARENB|PARODD|CMSPAR)) != - (old_cflag & (PARENB|PARODD|CMSPAR))) { - cp210x_get_line_ctl(port, &bits); - bits &= ~BITS_PARITY_MASK; - if (cflag & PARENB) { - if (cflag & CMSPAR) { - if (cflag & PARODD) { - bits |= BITS_PARITY_MARK; - dev_dbg(dev, "%s - parity = MARK\n", __func__); - } else { - bits |= BITS_PARITY_SPACE; - dev_dbg(dev, "%s - parity = SPACE\n", __func__); - } - } else { - if (cflag & PARODD) { - bits |= BITS_PARITY_ODD; - dev_dbg(dev, "%s - parity = ODD\n", __func__); - } else { - bits |= BITS_PARITY_EVEN; - dev_dbg(dev, "%s - parity = EVEN\n", __func__); - } - } - } - if (cp210x_write_u16_reg(port, CP210X_SET_LINE_CTL, bits)) - dev_dbg(dev, "Parity mode not supported by device\n"); + bits = 0; + + switch (C_CSIZE(tty)) { + case CS5: + bits |= BITS_DATA_5; + break; + case CS6: + bits |= BITS_DATA_6; + break; + case CS7: + bits |= BITS_DATA_7; + break; + case CS8: + default: + bits |= BITS_DATA_8; + break; } - if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) { - cp210x_get_line_ctl(port, &bits); - bits &= ~BITS_STOP_MASK; - if (cflag & CSTOPB) { - bits |= BITS_STOP_2; - dev_dbg(dev, "%s - stop bits = 2\n", __func__); + if (C_PARENB(tty)) { + if (C_CMSPAR(tty)) { + if (C_PARODD(tty)) + bits |= BITS_PARITY_MARK; + else + bits |= BITS_PARITY_SPACE; } else { - bits |= BITS_STOP_1; - dev_dbg(dev, "%s - stop bits = 1\n", __func__); + if (C_PARODD(tty)) + bits |= BITS_PARITY_ODD; + else + bits |= BITS_PARITY_EVEN; } - if (cp210x_write_u16_reg(port, CP210X_SET_LINE_CTL, bits)) - dev_dbg(dev, "Number of stop bits requested not supported by device\n"); } + if (C_CSTOPB(tty)) + bits |= BITS_STOP_2; + else + bits |= BITS_STOP_1; + + ret = cp210x_write_u16_reg(port, CP210X_SET_LINE_CTL, bits); + if (ret) + dev_err(&port->dev, "failed to set line control: %d\n", ret); + if ((cflag & CRTSCTS) != (old_cflag & CRTSCTS)) { struct cp210x_flow_ctl flow_ctl; u32 ctl_hs; From 46827bda2dd635aed8af0a232a8992d1481140d6 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 16 Nov 2020 17:18:23 +0100 Subject: [PATCH 103/172] USB: serial: cp210x: set terminal settings on open Unlike other drivers cp210x have been retrieving the current terminal settings from the device on open and reflecting those in termios. Due to how set_termios() used to be implemented, this saved a few control requests on open but has instead caused problems like broken flow control and has required adding workarounds for swapped line-control in cp2108 and line-speed initialisation on cp2104. This unusual implementation also complicates adding new features for no good reason. Rip out the corresponding code and the above mentioned workarounds and instead initialise the terminal settings unconditionally on open. Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 289 +----------------------------------- 1 file changed, 6 insertions(+), 283 deletions(-) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index ad134e517849..bb9c3d7ccaee 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -31,9 +31,6 @@ */ static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *); static void cp210x_close(struct usb_serial_port *); -static void cp210x_get_termios(struct tty_struct *, struct usb_serial_port *); -static void cp210x_get_termios_port(struct usb_serial_port *port, - tcflag_t *cflagp, unsigned int *baudp); static void cp210x_change_speed(struct tty_struct *, struct usb_serial_port *, struct ktermios *); static void cp210x_set_termios(struct tty_struct *, struct usb_serial_port *, @@ -267,7 +264,6 @@ enum cp210x_event_state { struct cp210x_port_private { u8 bInterfaceNumber; - bool has_swapped_line_ctl; bool event_mode; enum cp210x_event_state event_state; u8 lsr; @@ -593,46 +589,6 @@ static int cp210x_read_reg_block(struct usb_serial_port *port, u8 req, return result; } -/* - * Reads any 32-bit CP210X_ register identified by req. - */ -static int cp210x_read_u32_reg(struct usb_serial_port *port, u8 req, u32 *val) -{ - __le32 le32_val; - int err; - - err = cp210x_read_reg_block(port, req, &le32_val, sizeof(le32_val)); - if (err) { - /* - * FIXME Some callers don't bother to check for error, - * at least give them consistent junk until they are fixed - */ - *val = 0; - return err; - } - - *val = le32_to_cpu(le32_val); - - return 0; -} - -/* - * Reads any 16-bit CP210X_ register identified by req. - */ -static int cp210x_read_u16_reg(struct usb_serial_port *port, u8 req, u16 *val) -{ - __le16 le16_val; - int err; - - err = cp210x_read_reg_block(port, req, &le16_val, sizeof(le16_val)); - if (err) - return err; - - *val = le16_to_cpu(le16_val); - - return 0; -} - /* * Reads any 8-bit CP210X_ register identified by req. */ @@ -780,59 +736,6 @@ static int cp210x_write_vendor_block(struct usb_serial *serial, u8 type, } #endif -/* - * Detect CP2108 GET_LINE_CTL bug and activate workaround. - * Write a known good value 0x800, read it back. - * If it comes back swapped the bug is detected. - * Preserve the original register value. - */ -static int cp210x_detect_swapped_line_ctl(struct usb_serial_port *port) -{ - struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); - u16 line_ctl_save; - u16 line_ctl_test; - int err; - - err = cp210x_read_u16_reg(port, CP210X_GET_LINE_CTL, &line_ctl_save); - if (err) - return err; - - err = cp210x_write_u16_reg(port, CP210X_SET_LINE_CTL, 0x800); - if (err) - return err; - - err = cp210x_read_u16_reg(port, CP210X_GET_LINE_CTL, &line_ctl_test); - if (err) - return err; - - if (line_ctl_test == 8) { - port_priv->has_swapped_line_ctl = true; - line_ctl_save = swab16(line_ctl_save); - } - - return cp210x_write_u16_reg(port, CP210X_SET_LINE_CTL, line_ctl_save); -} - -/* - * Must always be called instead of cp210x_read_u16_reg(CP210X_GET_LINE_CTL) - * to workaround cp2108 bug and get correct value. - */ -static int cp210x_get_line_ctl(struct usb_serial_port *port, u16 *ctl) -{ - struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); - int err; - - err = cp210x_read_u16_reg(port, CP210X_GET_LINE_CTL, ctl); - if (err) - return err; - - /* Workaround swapped bytes in 16-bit value from CP210X_GET_LINE_CTL */ - if (port_priv->has_swapped_line_ctl) - *ctl = swab16(*ctl); - - return 0; -} - static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port) { struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); @@ -844,16 +747,8 @@ static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port) return result; } - /* Configure the termios structure */ - cp210x_get_termios(tty, port); - - if (tty) { - /* The baud rate must be initialised on cp2104 */ - cp210x_change_speed(tty, port, NULL); - - if (I_INPCK(tty)) - cp210x_enable_event_mode(port); - } + if (tty) + cp210x_set_termios(tty, port, NULL); result = usb_serial_generic_open(tty, port); if (result) @@ -1032,167 +927,6 @@ static bool cp210x_tx_empty(struct usb_serial_port *port) return !count; } -/* - * cp210x_get_termios - * Reads the baud rate, data bits, parity, stop bits and flow control mode - * from the device, corrects any unsupported values, and configures the - * termios structure to reflect the state of the device - */ -static void cp210x_get_termios(struct tty_struct *tty, - struct usb_serial_port *port) -{ - unsigned int baud; - - if (tty) { - cp210x_get_termios_port(tty->driver_data, - &tty->termios.c_cflag, &baud); - tty_encode_baud_rate(tty, baud, baud); - } else { - tcflag_t cflag; - cflag = 0; - cp210x_get_termios_port(port, &cflag, &baud); - } -} - -/* - * cp210x_get_termios_port - * This is the heart of cp210x_get_termios which always uses a &usb_serial_port. - */ -static void cp210x_get_termios_port(struct usb_serial_port *port, - tcflag_t *cflagp, unsigned int *baudp) -{ - struct device *dev = &port->dev; - tcflag_t cflag; - struct cp210x_flow_ctl flow_ctl; - u32 baud; - u16 bits; - u32 ctl_hs; - u32 flow_repl; - - cp210x_read_u32_reg(port, CP210X_GET_BAUDRATE, &baud); - - dev_dbg(dev, "%s - baud rate = %d\n", __func__, baud); - *baudp = baud; - - cflag = *cflagp; - - cp210x_get_line_ctl(port, &bits); - cflag &= ~CSIZE; - switch (bits & BITS_DATA_MASK) { - case BITS_DATA_5: - dev_dbg(dev, "%s - data bits = 5\n", __func__); - cflag |= CS5; - break; - case BITS_DATA_6: - dev_dbg(dev, "%s - data bits = 6\n", __func__); - cflag |= CS6; - break; - case BITS_DATA_7: - dev_dbg(dev, "%s - data bits = 7\n", __func__); - cflag |= CS7; - break; - case BITS_DATA_8: - dev_dbg(dev, "%s - data bits = 8\n", __func__); - cflag |= CS8; - break; - case BITS_DATA_9: - dev_dbg(dev, "%s - data bits = 9 (not supported, using 8 data bits)\n", __func__); - cflag |= CS8; - bits &= ~BITS_DATA_MASK; - bits |= BITS_DATA_8; - cp210x_write_u16_reg(port, CP210X_SET_LINE_CTL, bits); - break; - default: - dev_dbg(dev, "%s - Unknown number of data bits, using 8\n", __func__); - cflag |= CS8; - bits &= ~BITS_DATA_MASK; - bits |= BITS_DATA_8; - cp210x_write_u16_reg(port, CP210X_SET_LINE_CTL, bits); - break; - } - - switch (bits & BITS_PARITY_MASK) { - case BITS_PARITY_NONE: - dev_dbg(dev, "%s - parity = NONE\n", __func__); - cflag &= ~PARENB; - break; - case BITS_PARITY_ODD: - dev_dbg(dev, "%s - parity = ODD\n", __func__); - cflag |= (PARENB|PARODD); - break; - case BITS_PARITY_EVEN: - dev_dbg(dev, "%s - parity = EVEN\n", __func__); - cflag &= ~PARODD; - cflag |= PARENB; - break; - case BITS_PARITY_MARK: - dev_dbg(dev, "%s - parity = MARK\n", __func__); - cflag |= (PARENB|PARODD|CMSPAR); - break; - case BITS_PARITY_SPACE: - dev_dbg(dev, "%s - parity = SPACE\n", __func__); - cflag &= ~PARODD; - cflag |= (PARENB|CMSPAR); - break; - default: - dev_dbg(dev, "%s - Unknown parity mode, disabling parity\n", __func__); - cflag &= ~PARENB; - bits &= ~BITS_PARITY_MASK; - cp210x_write_u16_reg(port, CP210X_SET_LINE_CTL, bits); - break; - } - - cflag &= ~CSTOPB; - switch (bits & BITS_STOP_MASK) { - case BITS_STOP_1: - dev_dbg(dev, "%s - stop bits = 1\n", __func__); - break; - case BITS_STOP_1_5: - dev_dbg(dev, "%s - stop bits = 1.5 (not supported, using 1 stop bit)\n", __func__); - bits &= ~BITS_STOP_MASK; - cp210x_write_u16_reg(port, CP210X_SET_LINE_CTL, bits); - break; - case BITS_STOP_2: - dev_dbg(dev, "%s - stop bits = 2\n", __func__); - cflag |= CSTOPB; - break; - default: - dev_dbg(dev, "%s - Unknown number of stop bits, using 1 stop bit\n", __func__); - bits &= ~BITS_STOP_MASK; - cp210x_write_u16_reg(port, CP210X_SET_LINE_CTL, bits); - break; - } - - cp210x_read_reg_block(port, CP210X_GET_FLOW, &flow_ctl, - sizeof(flow_ctl)); - ctl_hs = le32_to_cpu(flow_ctl.ulControlHandshake); - if (ctl_hs & CP210X_SERIAL_CTS_HANDSHAKE) { - dev_dbg(dev, "%s - flow control = CRTSCTS\n", __func__); - /* - * When the port is closed, the CP210x hardware disables - * auto-RTS and RTS is deasserted but it leaves auto-CTS when - * in hardware flow control mode. When re-opening the port, if - * auto-CTS is enabled on the cp210x, then auto-RTS must be - * re-enabled in the driver. - */ - flow_repl = le32_to_cpu(flow_ctl.ulFlowReplace); - flow_repl &= ~CP210X_SERIAL_RTS_MASK; - flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_FLOW_CTL); - flow_ctl.ulFlowReplace = cpu_to_le32(flow_repl); - cp210x_write_reg_block(port, - CP210X_SET_FLOW, - &flow_ctl, - sizeof(flow_ctl)); - - cflag |= CRTSCTS; - } else { - dev_dbg(dev, "%s - flow control = NONE\n", __func__); - cflag &= ~CRTSCTS; - } - - *cflagp = cflag; -} - struct cp210x_rate { speed_t rate; speed_t high; @@ -1366,17 +1100,13 @@ static void cp210x_set_termios(struct tty_struct *tty, { struct cp210x_serial_private *priv = usb_get_serial_data(port->serial); struct device *dev = &port->dev; - unsigned int cflag, old_cflag; u16 bits; int ret; - if (!cp210x_termios_change(&tty->termios, old_termios)) + if (old_termios && !cp210x_termios_change(&tty->termios, old_termios)) return; - cflag = tty->termios.c_cflag; - old_cflag = old_termios->c_cflag; - - if (tty->termios.c_ospeed != old_termios->c_ospeed) + if (!old_termios || tty->termios.c_ospeed != old_termios->c_ospeed) cp210x_change_speed(tty, port, old_termios); /* CP2101 only supports CS8, 1 stop bit and non-stick parity. */ @@ -1426,7 +1156,7 @@ static void cp210x_set_termios(struct tty_struct *tty, if (ret) dev_err(&port->dev, "failed to set line control: %d\n", ret); - if ((cflag & CRTSCTS) != (old_cflag & CRTSCTS)) { + if (!old_termios || C_CRTSCTS(tty) != (old_termios->c_cflag & CRTSCTS)) { struct cp210x_flow_ctl flow_ctl; u32 ctl_hs; u32 flow_repl; @@ -1443,7 +1173,7 @@ static void cp210x_set_termios(struct tty_struct *tty, ctl_hs &= ~CP210X_SERIAL_DSR_SENSITIVITY; ctl_hs &= ~CP210X_SERIAL_DTR_MASK; ctl_hs |= CP210X_SERIAL_DTR_SHIFT(CP210X_SERIAL_DTR_ACTIVE); - if (cflag & CRTSCTS) { + if (C_CRTSCTS(tty)) { ctl_hs |= CP210X_SERIAL_CTS_HANDSHAKE; flow_repl &= ~CP210X_SERIAL_RTS_MASK; @@ -1979,7 +1709,6 @@ static int cp210x_port_probe(struct usb_serial_port *port) { struct usb_serial *serial = port->serial; struct cp210x_port_private *port_priv; - int ret; port_priv = kzalloc(sizeof(*port_priv), GFP_KERNEL); if (!port_priv) @@ -1989,12 +1718,6 @@ static int cp210x_port_probe(struct usb_serial_port *port) usb_set_serial_port_data(port, port_priv); - ret = cp210x_detect_swapped_line_ctl(port); - if (ret) { - kfree(port_priv); - return ret; - } - return 0; } From b339628ec08c51c3445f4b094e0011406fc36344 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 16 Nov 2020 17:18:24 +0100 Subject: [PATCH 104/172] USB: serial: cp210x: drop flow-control debugging Drop some unnecessary flow-control debugging. Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index bb9c3d7ccaee..04d2b15ceded 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -1165,8 +1165,6 @@ static void cp210x_set_termios(struct tty_struct *tty, sizeof(flow_ctl)); ctl_hs = le32_to_cpu(flow_ctl.ulControlHandshake); flow_repl = le32_to_cpu(flow_ctl.ulFlowReplace); - dev_dbg(dev, "%s - read ulControlHandshake=0x%08x, ulFlowReplace=0x%08x\n", - __func__, ctl_hs, flow_repl); ctl_hs &= ~CP210X_SERIAL_DSR_HANDSHAKE; ctl_hs &= ~CP210X_SERIAL_DCD_HANDSHAKE; @@ -1179,17 +1177,15 @@ static void cp210x_set_termios(struct tty_struct *tty, flow_repl &= ~CP210X_SERIAL_RTS_MASK; flow_repl |= CP210X_SERIAL_RTS_SHIFT( CP210X_SERIAL_RTS_FLOW_CTL); - dev_dbg(dev, "%s - flow control = CRTSCTS\n", __func__); } else { ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE; flow_repl &= ~CP210X_SERIAL_RTS_MASK; flow_repl |= CP210X_SERIAL_RTS_SHIFT( CP210X_SERIAL_RTS_ACTIVE); - dev_dbg(dev, "%s - flow control = NONE\n", __func__); } - dev_dbg(dev, "%s - write ulControlHandshake=0x%08x, ulFlowReplace=0x%08x\n", + dev_dbg(dev, "%s - ulControlHandshake=0x%08x, ulFlowReplace=0x%08x\n", __func__, ctl_hs, flow_repl); flow_ctl.ulControlHandshake = cpu_to_le32(ctl_hs); flow_ctl.ulFlowReplace = cpu_to_le32(flow_repl); From ed921771ffb65ce1f65d746fe8f88c0e0a829d76 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 16 Nov 2020 17:18:25 +0100 Subject: [PATCH 105/172] USB: serial: cp210x: refactor flow-control handling Add a helper function to be used to configure flow control. The flow-control code was the last caller that relied on the memset-on-failure behaviour of cp210x_read_reg_block(), which we can now drop in favour of bailing out on errors when retrieving the flow-control settings. This should also simplify adding support for software flow control. Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 97 ++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 50 deletions(-) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 04d2b15ceded..c77fd09b91ce 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -555,14 +555,8 @@ static int cp210x_read_reg_block(struct usb_serial_port *port, u8 req, int result; dmabuf = kmalloc(bufsize, GFP_KERNEL); - if (!dmabuf) { - /* - * FIXME Some callers don't bother to check for error, - * at least give them consistent junk until they are fixed - */ - memset(buf, 0, bufsize); + if (!dmabuf) return -ENOMEM; - } result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), req, REQTYPE_INTERFACE_TO_HOST, 0, @@ -576,12 +570,6 @@ static int cp210x_read_reg_block(struct usb_serial_port *port, u8 req, req, bufsize, result); if (result >= 0) result = -EIO; - - /* - * FIXME Some callers don't bother to check for error, - * at least give them consistent junk until they are fixed - */ - memset(buf, 0, bufsize); } kfree(dmabuf); @@ -1095,11 +1083,55 @@ static bool cp210x_termios_change(const struct ktermios *a, const struct ktermio return tty_termios_hw_change(a, b) || iflag_change; } +static void cp210x_set_flow_control(struct tty_struct *tty, + struct usb_serial_port *port, struct ktermios *old_termios) +{ + struct cp210x_flow_ctl flow_ctl; + u32 flow_repl; + u32 ctl_hs; + int ret; + + if (old_termios && C_CRTSCTS(tty) == (old_termios->c_cflag & CRTSCTS)) + return; + + ret = cp210x_read_reg_block(port, CP210X_GET_FLOW, &flow_ctl, + sizeof(flow_ctl)); + if (ret) + return; + + ctl_hs = le32_to_cpu(flow_ctl.ulControlHandshake); + flow_repl = le32_to_cpu(flow_ctl.ulFlowReplace); + + ctl_hs &= ~CP210X_SERIAL_DSR_HANDSHAKE; + ctl_hs &= ~CP210X_SERIAL_DCD_HANDSHAKE; + ctl_hs &= ~CP210X_SERIAL_DSR_SENSITIVITY; + ctl_hs &= ~CP210X_SERIAL_DTR_MASK; + ctl_hs |= CP210X_SERIAL_DTR_SHIFT(CP210X_SERIAL_DTR_ACTIVE); + + if (C_CRTSCTS(tty)) { + ctl_hs |= CP210X_SERIAL_CTS_HANDSHAKE; + flow_repl &= ~CP210X_SERIAL_RTS_MASK; + flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_FLOW_CTL); + } else { + ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE; + flow_repl &= ~CP210X_SERIAL_RTS_MASK; + flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_ACTIVE); + } + + dev_dbg(&port->dev, "%s - ulControlHandshake=0x%08x, ulFlowReplace=0x%08x\n", + __func__, ctl_hs, flow_repl); + + flow_ctl.ulControlHandshake = cpu_to_le32(ctl_hs); + flow_ctl.ulFlowReplace = cpu_to_le32(flow_repl); + + cp210x_write_reg_block(port, CP210X_SET_FLOW, &flow_ctl, + sizeof(flow_ctl)); +} + static void cp210x_set_termios(struct tty_struct *tty, struct usb_serial_port *port, struct ktermios *old_termios) { struct cp210x_serial_private *priv = usb_get_serial_data(port->serial); - struct device *dev = &port->dev; u16 bits; int ret; @@ -1156,42 +1188,7 @@ static void cp210x_set_termios(struct tty_struct *tty, if (ret) dev_err(&port->dev, "failed to set line control: %d\n", ret); - if (!old_termios || C_CRTSCTS(tty) != (old_termios->c_cflag & CRTSCTS)) { - struct cp210x_flow_ctl flow_ctl; - u32 ctl_hs; - u32 flow_repl; - - cp210x_read_reg_block(port, CP210X_GET_FLOW, &flow_ctl, - sizeof(flow_ctl)); - ctl_hs = le32_to_cpu(flow_ctl.ulControlHandshake); - flow_repl = le32_to_cpu(flow_ctl.ulFlowReplace); - - ctl_hs &= ~CP210X_SERIAL_DSR_HANDSHAKE; - ctl_hs &= ~CP210X_SERIAL_DCD_HANDSHAKE; - ctl_hs &= ~CP210X_SERIAL_DSR_SENSITIVITY; - ctl_hs &= ~CP210X_SERIAL_DTR_MASK; - ctl_hs |= CP210X_SERIAL_DTR_SHIFT(CP210X_SERIAL_DTR_ACTIVE); - if (C_CRTSCTS(tty)) { - ctl_hs |= CP210X_SERIAL_CTS_HANDSHAKE; - - flow_repl &= ~CP210X_SERIAL_RTS_MASK; - flow_repl |= CP210X_SERIAL_RTS_SHIFT( - CP210X_SERIAL_RTS_FLOW_CTL); - } else { - ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE; - - flow_repl &= ~CP210X_SERIAL_RTS_MASK; - flow_repl |= CP210X_SERIAL_RTS_SHIFT( - CP210X_SERIAL_RTS_ACTIVE); - } - - dev_dbg(dev, "%s - ulControlHandshake=0x%08x, ulFlowReplace=0x%08x\n", - __func__, ctl_hs, flow_repl); - flow_ctl.ulControlHandshake = cpu_to_le32(ctl_hs); - flow_ctl.ulFlowReplace = cpu_to_le32(flow_repl); - cp210x_write_reg_block(port, CP210X_SET_FLOW, &flow_ctl, - sizeof(flow_ctl)); - } + cp210x_set_flow_control(tty, port, old_termios); /* * Enable event-insertion mode only if input parity checking is From daa919196be49815e342eb79fab81852102703c2 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 16 Nov 2020 17:18:26 +0100 Subject: [PATCH 106/172] USB: serial: cp210x: clean up dtr_rts() Clean up dtr_rts() by renaming the port parameter and adding missing whitespace. Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index c77fd09b91ce..fbb10dfc56e3 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -46,7 +46,7 @@ static void cp210x_disconnect(struct usb_serial *); static void cp210x_release(struct usb_serial *); static int cp210x_port_probe(struct usb_serial_port *); static int cp210x_port_remove(struct usb_serial_port *); -static void cp210x_dtr_rts(struct usb_serial_port *p, int on); +static void cp210x_dtr_rts(struct usb_serial_port *port, int on); static void cp210x_process_read_urb(struct urb *urb); static void cp210x_enable_event_mode(struct usb_serial_port *port); static void cp210x_disable_event_mode(struct usb_serial_port *port); @@ -1234,12 +1234,12 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port, return cp210x_write_u16_reg(port, CP210X_SET_MHS, control); } -static void cp210x_dtr_rts(struct usb_serial_port *p, int on) +static void cp210x_dtr_rts(struct usb_serial_port *port, int on) { if (on) - cp210x_tiocmset_port(p, TIOCM_DTR|TIOCM_RTS, 0); + cp210x_tiocmset_port(port, TIOCM_DTR | TIOCM_RTS, 0); else - cp210x_tiocmset_port(p, 0, TIOCM_DTR|TIOCM_RTS); + cp210x_tiocmset_port(port, 0, TIOCM_DTR | TIOCM_RTS); } static int cp210x_tiocmget(struct tty_struct *tty) From cf5fbe02042e52a5a3d93223770db39002b313c6 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 3 Dec 2020 11:42:10 +0300 Subject: [PATCH 107/172] USB: apple-mfi-fastcharge: Fix use after free in probe This code frees "mfi" and then derefences it on the next line to get the error code. Fixes: b0eec52fbe63 ("USB: apple-mfi-fastcharge: Fix kfree after failed kzalloc") Reviewed-by: Bastien Nocera Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/X8ik4j8yJitVUyfU@mwanda Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/apple-mfi-fastcharge.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/misc/apple-mfi-fastcharge.c b/drivers/usb/misc/apple-mfi-fastcharge.c index 6dedd5498e8a..ac8695195c13 100644 --- a/drivers/usb/misc/apple-mfi-fastcharge.c +++ b/drivers/usb/misc/apple-mfi-fastcharge.c @@ -178,6 +178,7 @@ static int mfi_fc_probe(struct usb_device *udev) { struct power_supply_config battery_cfg = {}; struct mfi_device *mfi = NULL; + int err; if (!mfi_fc_match(udev)) return -ENODEV; @@ -194,8 +195,9 @@ static int mfi_fc_probe(struct usb_device *udev) &battery_cfg); if (IS_ERR(mfi->battery)) { dev_err(&udev->dev, "Can't register battery\n"); + err = PTR_ERR(mfi->battery); kfree(mfi); - return PTR_ERR(mfi->battery); + return err; } mfi->udev = usb_get_dev(udev); From baf7df456b3848e4b6f8648e754e3a0f77fe700e Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 4 Dec 2020 09:51:09 +0100 Subject: [PATCH 108/172] USB: core: drop short-transfer check from usb_control_msg_send() A failure to send a complete control message is always an error so there's no need to check for short transfers in usb_control_msg_send(). Signed-off-by: Johan Hovold Link: https://lore.kernel.org/r/20201204085110.20055-3-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/message.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index c4e876050074..0d8f75e94d46 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -219,9 +219,8 @@ int usb_control_msg_send(struct usb_device *dev, __u8 endpoint, __u8 request, if (ret < 0) return ret; - if (ret == size) - return 0; - return -EINVAL; + + return 0; } EXPORT_SYMBOL_GPL(usb_control_msg_send); From 9dc9c8543aa0b9ef8852330b27cd2eef337bea18 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 4 Dec 2020 09:51:10 +0100 Subject: [PATCH 109/172] USB: core: return -EREMOTEIO on short usb_control_msg_recv() Return -EREMOTEIO instead of -EINVAL on short control transfers when using the new usb_control_msg_recv() helper. EINVAL is used to report invalid arguments (e.g. to the helper) and should not be used for unrelated errors. Many driver currently return -EIO on short control transfers but since host-controller drivers already use -EREMOTEIO for short transfers whenever the URB_SHORT_NOT_OK flag is set, let's use that here as well. This also allows usb_control_msg_recv() to eventually use URB_SHORT_NOT_OK without changing the return value again. Signed-off-by: Johan Hovold Link: https://lore.kernel.org/r/20201204085110.20055-4-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/message.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 0d8f75e94d46..d92a04de0c50 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -289,7 +289,7 @@ int usb_control_msg_recv(struct usb_device *dev, __u8 endpoint, __u8 request, memcpy(driver_data, data, size); ret = 0; } else { - ret = -EINVAL; + ret = -EREMOTEIO; } exit: From e3541d5de544da54a1770e9c82c55f2a6f6b3b67 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 3 Dec 2020 11:41:35 +0300 Subject: [PATCH 110/172] usb: mtu3: mtu3_debug: remove an unused struct member The "nregs" member is not used. The code uses the "regset.nregs" struct member instead. Acked-by: Chunfeng Yun Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/X8ikv1QA3Do50D+R@mwanda Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_debug.h | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/mtu3/mtu3_debug.h b/drivers/usb/mtu3/mtu3_debug.h index 3084c46017c3..9a36134b322d 100644 --- a/drivers/usb/mtu3/mtu3_debug.h +++ b/drivers/usb/mtu3/mtu3_debug.h @@ -19,7 +19,6 @@ struct ssusb_mtk; struct mtu3_regset { char name[MTU3_DEBUGFS_NAME_LEN]; struct debugfs_regset32 regset; - size_t nregs; }; struct mtu3_file_map { From 7fe53dcbbfbd91ad953022281adcc6cbc9dbc052 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 4 Dec 2020 09:51:08 +0100 Subject: [PATCH 111/172] USB: core: drop pipe-type check from new control-message helpers The new control-message helpers include a pipe-type check which is almost completely redundant. Control messages are generally sent to the default pipe which always exists and is of the correct type since its endpoint representation is created by USB core as part of enumeration for all devices. There is currently only one instance of a driver in the tree which use a control endpoint other than endpoint 0 (and it does not use the new helpers). Drivers should be testing for the existence of their resources at probe rather than at runtime, but to catch drivers failing to do so USB core already does a sanity check on URB submission and triggers a WARN(). Having the same sanity check done in the helper only suppresses the warning without allowing us to find and fix the drivers. Signed-off-by: Johan Hovold Link: https://lore.kernel.org/r/20201204085110.20055-2-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/message.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index d92a04de0c50..30e9e680c74c 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -204,9 +204,6 @@ int usb_control_msg_send(struct usb_device *dev, __u8 endpoint, __u8 request, int ret; u8 *data = NULL; - if (usb_pipe_type_check(dev, pipe)) - return -EINVAL; - if (size) { data = kmemdup(driver_data, size, memflags); if (!data) @@ -272,7 +269,7 @@ int usb_control_msg_recv(struct usb_device *dev, __u8 endpoint, __u8 request, int ret; u8 *data; - if (!size || !driver_data || usb_pipe_type_check(dev, pipe)) + if (!size || !driver_data) return -EINVAL; data = kmalloc(size, memflags); From c7721e15f434920145c376e8fe77e1c079fc3726 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Mon, 7 Dec 2020 10:09:09 +0800 Subject: [PATCH 112/172] usb: chipidea: ci_hdrc_imx: Pass DISABLE_DEVICE_STREAMING flag to imx6ul According to the i.MX6UL Errata document: https://www.nxp.com/docs/en/errata/IMX6ULCE.pdf ERR007881 also affects i.MX6UL, so pass the CI_HDRC_DISABLE_DEVICE_STREAMING flag to workaround the issue. Fixes: 52fe568e5d71 ("usb: chipidea: imx: add imx6ul usb support") Cc: Signed-off-by: Fabio Estevam Signed-off-by: Peter Chen Link: https://lore.kernel.org/r/20201207020909.22483-2-peter.chen@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci_hdrc_imx.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 25c65accf089..5e07a0a86d11 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -57,7 +57,8 @@ static const struct ci_hdrc_imx_platform_flag imx6sx_usb_data = { static const struct ci_hdrc_imx_platform_flag imx6ul_usb_data = { .flags = CI_HDRC_SUPPORTS_RUNTIME_PM | - CI_HDRC_TURN_VBUS_EARLY_ON, + CI_HDRC_TURN_VBUS_EARLY_ON | + CI_HDRC_DISABLE_DEVICE_STREAMING, }; static const struct ci_hdrc_imx_platform_flag imx7d_usb_data = { From e90cfa813da7a527785033a0b247594c2de93dd8 Mon Sep 17 00:00:00 2001 From: Bui Quang Minh Date: Fri, 4 Dec 2020 06:24:49 +0000 Subject: [PATCH 113/172] USB: dummy-hcd: Fix uninitialized array use in init() This error path err_add_pdata: for (i = 0; i < mod_data.num; i++) kfree(dum[i]); can be triggered when not all dum's elements are initialized. Fix this by initializing all dum's elements to NULL. Acked-by: Alan Stern Cc: stable Signed-off-by: Bui Quang Minh Link: https://lore.kernel.org/r/1607063090-3426-1-git-send-email-minhquangbui99@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/dummy_hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index f6b407778179..ab5e978b5052 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -2738,7 +2738,7 @@ static int __init init(void) { int retval = -ENOMEM; int i; - struct dummy *dum[MAX_NUM_UDC]; + struct dummy *dum[MAX_NUM_UDC] = {}; if (usb_disabled()) return -ENODEV; From 3f6f6343a29d9ea7429306b83b18e66dc1331d5c Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 3 Dec 2020 11:41:13 +0300 Subject: [PATCH 114/172] usb: mtu3: fix memory corruption in mtu3_debugfs_regset() This code is using the wrong sizeof() so it does not allocate enough memory. It allocates 32 bytes but 72 are required. That will lead to memory corruption. Fixes: ae07809255d3 ("usb: mtu3: add debugfs interface files") Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/X8ikqc4Mo2/0G72j@mwanda Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_debugfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/mtu3/mtu3_debugfs.c b/drivers/usb/mtu3/mtu3_debugfs.c index fdeade6254ae..7537bfd651af 100644 --- a/drivers/usb/mtu3/mtu3_debugfs.c +++ b/drivers/usb/mtu3/mtu3_debugfs.c @@ -127,7 +127,7 @@ static void mtu3_debugfs_regset(struct mtu3 *mtu, void __iomem *base, struct debugfs_regset32 *regset; struct mtu3_regset *mregs; - mregs = devm_kzalloc(mtu->dev, sizeof(*regset), GFP_KERNEL); + mregs = devm_kzalloc(mtu->dev, sizeof(*mregs), GFP_KERNEL); if (!mregs) return; From ff62d08fd6242a3ef5aa0a7bcae6a0b0136a60ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Sat, 5 Dec 2020 12:13:25 +0100 Subject: [PATCH 115/172] usb: typec: tps6598x: Select USB_ROLE_SWITCH and REGMAP_I2C MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is more in line with what tcpm does and will be needed to avoid recursive dependency like > drivers/power/supply/Kconfig:2:error: recursive dependency detected! drivers/power/supply/Kconfig:2: symbol POWER_SUPPLY is selected by TYPEC_TPS6598X drivers/usb/typec/Kconfig:64: symbol TYPEC_TPS6598X depends on REGMAP_I2C drivers/base/regmap/Kconfig:19: symbol REGMAP_I2C is selected by CHARGER_ADP5061 drivers/power/supply/Kconfig:93: symbol CHARGER_ADP5061 depends on POWER_SUPPLY For a resolution refer to Documentation/kbuild/kconfig-language.rst subsection "Kconfig recursive dependency limitations" when selecting POWER_SUPPLY. Reviewed-by: Heikki Krogerus Reviewed-by: Andy Shevchenko Signed-off-by: Guido Günther Link: https://lore.kernel.org/r/6d11417c42d82caf66e08af160397959eb7d0d60.1607166657.git.agx@sigxcpu.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index e7f120874c48..1b00120255c1 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -64,8 +64,8 @@ config TYPEC_HD3SS3220 config TYPEC_TPS6598X tristate "TI TPS6598x USB Power Delivery controller driver" depends on I2C - depends on REGMAP_I2C - depends on USB_ROLE_SWITCH || !USB_ROLE_SWITCH + select REGMAP_I2C + select USB_ROLE_SWITCH help Say Y or M here if your system has TI TPS65982 or TPS65983 USB Power Delivery controller. From 10eb0b6ac63a15b80e4e9ae8b85668827a747350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Sat, 5 Dec 2020 12:13:26 +0100 Subject: [PATCH 116/172] usb: typec: tps6598x: Export some power supply properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows downstream supplies and userspace to detect whether external power is supplied. Reviewed-by: Heikki Krogerus Reviewed-by: Andy Shevchenko Signed-off-by: Guido Günther Link: https://lore.kernel.org/r/2c8e81d9da9ff05b065f66edba915edd11f74065.1607166657.git.agx@sigxcpu.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/Kconfig | 1 + drivers/usb/typec/tps6598x.c | 103 +++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index 1b00120255c1..270e81c087e9 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -64,6 +64,7 @@ config TYPEC_HD3SS3220 config TYPEC_TPS6598X tristate "TI TPS6598x USB Power Delivery controller driver" depends on I2C + select POWER_SUPPLY select REGMAP_I2C select USB_ROLE_SWITCH help diff --git a/drivers/usb/typec/tps6598x.c b/drivers/usb/typec/tps6598x.c index 3db33bb622c3..6e6ef6317523 100644 --- a/drivers/usb/typec/tps6598x.c +++ b/drivers/usb/typec/tps6598x.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,7 @@ enum { }; /* TPS_REG_POWER_STATUS bits */ +#define TPS_POWER_STATUS_CONNECTION BIT(0) #define TPS_POWER_STATUS_SOURCESINK BIT(1) #define TPS_POWER_STATUS_PWROPMODE(p) (((p) & GENMASK(3, 2)) >> 2) @@ -96,8 +98,25 @@ struct tps6598x { struct typec_partner *partner; struct usb_pd_identity partner_identity; struct usb_role_switch *role_sw; + struct typec_capability typec_cap; + + struct power_supply *psy; + struct power_supply_desc psy_desc; + enum power_supply_usb_type usb_type; }; +static enum power_supply_property tps6598x_psy_props[] = { + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_ONLINE, +}; + +static enum power_supply_usb_type tps6598x_psy_usb_types[] = { + POWER_SUPPLY_USB_TYPE_C, + POWER_SUPPLY_USB_TYPE_PD, +}; + +static const char *tps6598x_psy_name_prefix = "tps6598x-source-psy-"; + /* * Max data bytes for Data1, Data2, and other registers. See ch 1.3.2: * https://www.ti.com/lit/ug/slvuan1a/slvuan1a.pdf @@ -248,6 +267,8 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status) if (desc.identity) typec_partner_set_identity(tps->partner); + power_supply_changed(tps->psy); + return 0; } @@ -260,6 +281,7 @@ static void tps6598x_disconnect(struct tps6598x *tps, u32 status) typec_set_pwr_role(tps->port, TPS_STATUS_PORTROLE(status)); typec_set_vconn_role(tps->port, TPS_STATUS_VCONN(status)); tps6598x_set_data_role(tps, TPS_STATUS_DATAROLE(status), false); + power_supply_changed(tps->psy); } static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd, @@ -467,6 +489,83 @@ static const struct regmap_config tps6598x_regmap_config = { .max_register = 0x7F, }; +static int tps6598x_psy_get_online(struct tps6598x *tps, + union power_supply_propval *val) +{ + int ret; + u16 pwr_status; + + ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &pwr_status); + if (ret < 0) + return ret; + + if ((pwr_status & TPS_POWER_STATUS_CONNECTION) && + (pwr_status & TPS_POWER_STATUS_SOURCESINK)) { + val->intval = 1; + } else { + val->intval = 0; + } + return 0; +} + +static int tps6598x_psy_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct tps6598x *tps = power_supply_get_drvdata(psy); + u16 pwr_status; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_USB_TYPE: + ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &pwr_status); + if (ret < 0) + return ret; + if (TPS_POWER_STATUS_PWROPMODE(pwr_status) == TYPEC_PWR_MODE_PD) + val->intval = POWER_SUPPLY_USB_TYPE_PD; + else + val->intval = POWER_SUPPLY_USB_TYPE_C; + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = tps6598x_psy_get_online(tps, val); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int devm_tps6598_psy_register(struct tps6598x *tps) +{ + struct power_supply_config psy_cfg = {}; + const char *port_dev_name = dev_name(tps->dev); + char *psy_name; + + psy_cfg.drv_data = tps; + psy_cfg.fwnode = dev_fwnode(tps->dev); + + psy_name = devm_kasprintf(tps->dev, GFP_KERNEL, "%s%s", tps6598x_psy_name_prefix, + port_dev_name); + if (!psy_name) + return -ENOMEM; + + tps->psy_desc.name = psy_name; + tps->psy_desc.type = POWER_SUPPLY_TYPE_USB; + tps->psy_desc.usb_types = tps6598x_psy_usb_types; + tps->psy_desc.num_usb_types = ARRAY_SIZE(tps6598x_psy_usb_types); + tps->psy_desc.properties = tps6598x_psy_props; + tps->psy_desc.num_properties = ARRAY_SIZE(tps6598x_psy_props); + tps->psy_desc.get_property = tps6598x_psy_get_prop; + + tps->usb_type = POWER_SUPPLY_USB_TYPE_C; + + tps->psy = devm_power_supply_register(tps->dev, &tps->psy_desc, + &psy_cfg); + return PTR_ERR_OR_ZERO(tps->psy); +} + static int tps6598x_probe(struct i2c_client *client) { struct typec_capability typec_cap = { }; @@ -560,6 +659,10 @@ static int tps6598x_probe(struct i2c_client *client) goto err_role_put; } + ret = devm_tps6598_psy_register(tps); + if (ret) + return ret; + tps->port = typec_register_port(&client->dev, &typec_cap); if (IS_ERR(tps->port)) { ret = PTR_ERR(tps->port); From 08a02f954b0def3ada8ed6d4b2c7bcb67e885e9c Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Mon, 7 Dec 2020 14:03:23 +0100 Subject: [PATCH 117/172] USB: add RESET_RESUME quirk for Snapscan 1212 I got reports that some models of this old scanner need this when using runtime PM. Signed-off-by: Oliver Neukum Cc: stable Link: https://lore.kernel.org/r/20201207130323.23857-1-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/quirks.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index fad31ccd1fa8..1b4eb7046b07 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -342,6 +342,9 @@ static const struct usb_device_id usb_quirk_list[] = { { USB_DEVICE(0x06a3, 0x0006), .driver_info = USB_QUIRK_CONFIG_INTF_STRINGS }, + /* Agfa SNAPSCAN 1212U */ + { USB_DEVICE(0x06bd, 0x0001), .driver_info = USB_QUIRK_RESET_RESUME }, + /* Guillemot Webcam Hercules Dualpix Exchange (2nd ID) */ { USB_DEVICE(0x06f8, 0x0804), .driver_info = USB_QUIRK_RESET_RESUME }, From b175d273d4e4100b66e68f0675fef7a3c07a7957 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 8 Dec 2020 11:30:42 -0500 Subject: [PATCH 118/172] USB: legotower: fix logical error in recent commit Commit d9f0d82f06c6 ("USB: legousbtower: use usb_control_msg_recv()") contained an elementary logical error. The check of the return code from the new usb_control_msg_recv() function was inverted. Reported-and-tested-by: syzbot+9be25235b7a69b24d117@syzkaller.appspotmail.com Signed-off-by: Alan Stern Link: https://lore.kernel.org/r/20201208163042.GD1298255@rowland.harvard.edu Fixes: d9f0d82f06c6 ("USB: legousbtower: use usb_control_msg_recv()") Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/legousbtower.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/misc/legousbtower.c b/drivers/usb/misc/legousbtower.c index ba655b4af4fc..1c9e09138c10 100644 --- a/drivers/usb/misc/legousbtower.c +++ b/drivers/usb/misc/legousbtower.c @@ -797,7 +797,7 @@ static int tower_probe(struct usb_interface *interface, const struct usb_device_ &get_version_reply, sizeof(get_version_reply), 1000, GFP_KERNEL); - if (!result) { + if (result) { dev_err(idev, "get version request failed: %d\n", result); retval = result; goto error; From bac1ec551434697ca3c5bb5d258811ba5446866a Mon Sep 17 00:00:00 2001 From: Tejas Joglekar Date: Tue, 8 Dec 2020 11:29:08 +0200 Subject: [PATCH 119/172] usb: xhci: Set quirk for XHCI_SG_TRB_CACHE_SIZE_QUIRK This commit uses the private data passed by parent device to set the quirk for Synopsys xHC. This patch fixes the SNPS xHC hang issue when the data is scattered across small buffers which does not make atleast MPS size for given TRB cache size of SNPS xHC. Signed-off-by: Tejas Joglekar Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20201208092912.1773650-2-mathias.nyman@linux.intel.com Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-plat.c | 3 +++ drivers/usb/host/xhci.h | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index aa2d35f98200..4d34f6005381 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -333,6 +333,9 @@ static int xhci_plat_probe(struct platform_device *pdev) if (priv && (priv->quirks & XHCI_SKIP_PHY_INIT)) hcd->skip_phy_initialization = 1; + if (priv && (priv->quirks & XHCI_SG_TRB_CACHE_SIZE_QUIRK)) + xhci->quirks |= XHCI_SG_TRB_CACHE_SIZE_QUIRK; + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); if (ret) goto disable_usb_phy; diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index ebb359ebb261..d90c0d5df3b3 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1878,6 +1878,7 @@ struct xhci_hcd { #define XHCI_RENESAS_FW_QUIRK BIT_ULL(36) #define XHCI_SKIP_PHY_INIT BIT_ULL(37) #define XHCI_DISABLE_SPARSE BIT_ULL(38) +#define XHCI_SG_TRB_CACHE_SIZE_QUIRK BIT_ULL(39) unsigned int num_active_eps; unsigned int limit_active_eps; From 2017a1e58472a27e532b9644b4a61dfe18f6baac Mon Sep 17 00:00:00 2001 From: Tejas Joglekar Date: Tue, 8 Dec 2020 11:29:09 +0200 Subject: [PATCH 120/172] usb: xhci: Use temporary buffer to consolidate SG The Synopsys xHC has an internal TRB cache of size TRB_CACHE_SIZE for each endpoint. The default value for TRB_CACHE_SIZE is 16 for SS and 8 for HS. The controller loads and updates the TRB cache from the transfer ring in system memory whenever the driver issues a start transfer or update transfer command. For chained TRBs, the Synopsys xHC requires that the total amount of bytes for all TRBs loaded in the TRB cache be greater than or equal to 1 MPS. Or the chain ends within the TRB cache (with a last TRB). If this requirement is not met, the controller will not be able to send or receive a packet and it will hang causing a driver timeout and error. This can be a problem if a class driver queues SG requests with many small-buffer entries. The XHCI driver will create a chained TRB for each entry which may trigger this issue. This patch adds logic to the XHCI driver to detect and prevent this from happening. For every (TRB_CACHE_SIZE - 2), we check the total buffer size of the SG list and if the last window of (TRB_CACHE_SIZE - 2) SG list length and we don't make up at least 1 MPS, we create a temporary buffer to consolidate full SG list into the buffer. We check at (TRB_CACHE_SIZE - 2) window because it is possible that there would be a link and/or event data TRB that take up to 2 of the cache entries. We discovered this issue with devices on other platforms but have not yet come across any device that triggers this on Linux. But it could be a real problem now or in the future. All it takes is N number of small chained TRBs. And other instances of the Synopsys IP may have smaller values for the TRB_CACHE_SIZE which would exacerbate the problem. Signed-off-by: Tejas Joglekar Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20201208092912.1773650-3-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 2 +- drivers/usb/host/xhci.c | 129 ++++++++++++++++++++++++++++++++++- drivers/usb/host/xhci.h | 4 ++ 3 files changed, 133 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index eac43a7b7f23..5677b81c0915 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -3327,7 +3327,7 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, full_len = urb->transfer_buffer_length; /* If we have scatter/gather list, we use it. */ - if (urb->num_sgs) { + if (urb->num_sgs && !(urb->transfer_flags & URB_DMA_MAP_SINGLE)) { num_sgs = urb->num_mapped_sgs; sg = urb->sg; addr = (u64) sg_dma_address(sg); diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 7f3ef84c220b..91ab81c3fc79 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1259,6 +1259,108 @@ EXPORT_SYMBOL_GPL(xhci_resume); /*-------------------------------------------------------------------------*/ +static int xhci_map_temp_buffer(struct usb_hcd *hcd, struct urb *urb) +{ + void *temp; + int ret = 0; + unsigned int buf_len; + enum dma_data_direction dir; + + dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + buf_len = urb->transfer_buffer_length; + + temp = kzalloc_node(buf_len, GFP_ATOMIC, + dev_to_node(hcd->self.sysdev)); + + if (usb_urb_dir_out(urb)) + sg_pcopy_to_buffer(urb->sg, urb->num_sgs, + temp, buf_len, 0); + + urb->transfer_buffer = temp; + urb->transfer_dma = dma_map_single(hcd->self.sysdev, + urb->transfer_buffer, + urb->transfer_buffer_length, + dir); + + if (dma_mapping_error(hcd->self.sysdev, + urb->transfer_dma)) { + ret = -EAGAIN; + kfree(temp); + } else { + urb->transfer_flags |= URB_DMA_MAP_SINGLE; + } + + return ret; +} + +static bool xhci_urb_temp_buffer_required(struct usb_hcd *hcd, + struct urb *urb) +{ + bool ret = false; + unsigned int i; + unsigned int len = 0; + unsigned int trb_size; + unsigned int max_pkt; + struct scatterlist *sg; + struct scatterlist *tail_sg; + + tail_sg = urb->sg; + max_pkt = usb_endpoint_maxp(&urb->ep->desc); + + if (!urb->num_sgs) + return ret; + + if (urb->dev->speed >= USB_SPEED_SUPER) + trb_size = TRB_CACHE_SIZE_SS; + else + trb_size = TRB_CACHE_SIZE_HS; + + if (urb->transfer_buffer_length != 0 && + !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)) { + for_each_sg(urb->sg, sg, urb->num_sgs, i) { + len = len + sg->length; + if (i > trb_size - 2) { + len = len - tail_sg->length; + if (len < max_pkt) { + ret = true; + break; + } + + tail_sg = sg_next(tail_sg); + } + } + } + return ret; +} + +static void xhci_unmap_temp_buf(struct usb_hcd *hcd, struct urb *urb) +{ + unsigned int len; + unsigned int buf_len; + enum dma_data_direction dir; + + dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + + buf_len = urb->transfer_buffer_length; + + if (IS_ENABLED(CONFIG_HAS_DMA) && + (urb->transfer_flags & URB_DMA_MAP_SINGLE)) + dma_unmap_single(hcd->self.sysdev, + urb->transfer_dma, + urb->transfer_buffer_length, + dir); + + if (usb_urb_dir_in(urb)) + len = sg_pcopy_from_buffer(urb->sg, urb->num_sgs, + urb->transfer_buffer, + buf_len, + 0); + + urb->transfer_flags &= ~URB_DMA_MAP_SINGLE; + kfree(urb->transfer_buffer); + urb->transfer_buffer = NULL; +} + /* * Bypass the DMA mapping if URB is suitable for Immediate Transfer (IDT), * we'll copy the actual data into the TRB address register. This is limited to @@ -1268,13 +1370,37 @@ EXPORT_SYMBOL_GPL(xhci_resume); static int xhci_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) { + struct xhci_hcd *xhci; + + xhci = hcd_to_xhci(hcd); + if (xhci_urb_suitable_for_idt(urb)) return 0; + if (xhci->quirks & XHCI_SG_TRB_CACHE_SIZE_QUIRK) { + if (xhci_urb_temp_buffer_required(hcd, urb)) + return xhci_map_temp_buffer(hcd, urb); + } return usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); } -/* +static void xhci_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) +{ + struct xhci_hcd *xhci; + bool unmap_temp_buf = false; + + xhci = hcd_to_xhci(hcd); + + if (urb->num_sgs && (urb->transfer_flags & URB_DMA_MAP_SINGLE)) + unmap_temp_buf = true; + + if ((xhci->quirks & XHCI_SG_TRB_CACHE_SIZE_QUIRK) && unmap_temp_buf) + xhci_unmap_temp_buf(hcd, urb); + else + usb_hcd_unmap_urb_for_dma(hcd, urb); +} + +/** * xhci_get_endpoint_index - Used for passing endpoint bitmasks between the core and * HCDs. Find the index for an endpoint given its descriptor. Use the return * value to right shift 1 for the bitmask. @@ -5327,6 +5453,7 @@ static const struct hc_driver xhci_hc_driver = { * managing i/o requests and associated device resources */ .map_urb_for_dma = xhci_map_urb_for_dma, + .unmap_urb_for_dma = xhci_unmap_urb_for_dma, .urb_enqueue = xhci_urb_enqueue, .urb_dequeue = xhci_urb_dequeue, .alloc_dev = xhci_alloc_dev, diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index d90c0d5df3b3..25e57bc9c3cc 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1330,6 +1330,10 @@ enum xhci_setup_dev { #define TRB_SIA (1<<31) #define TRB_FRAME_ID(p) (((p) & 0x7ff) << 20) +/* TRB cache size for xHC with TRB cache */ +#define TRB_CACHE_SIZE_HS 8 +#define TRB_CACHE_SIZE_SS 16 + struct xhci_generic_trb { __le32 field[4]; }; From c4d1ca05b8e68a4b5a3c4455cb6ec25b3df6d9dd Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 8 Dec 2020 11:29:10 +0200 Subject: [PATCH 121/172] xhci-pci: Allow host runtime PM as default for Intel Alpine Ridge LP The xHCI controller on Alpine Ridge LP keeps the whole Thunderbolt controller awake if the host controller is not allowed to sleep. This is the case even if no USB devices are connected to the host. Add the Intel Alpine Ridge LP product-id to the list of product-ids for which we allow runtime PM by default. Fixes: 2815ef7fe4d4 ("xhci-pci: allow host runtime PM as default for Intel Alpine and Titan Ridge") Cc: Reviewed-by: Mika Westerberg Signed-off-by: Hans de Goede Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20201208092912.1773650-4-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-pci.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index bf89172c43ca..5f94d7edeb37 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -47,6 +47,7 @@ #define PCI_DEVICE_ID_INTEL_DNV_XHCI 0x19d0 #define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_XHCI 0x15b5 #define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_XHCI 0x15b6 +#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_XHCI 0x15c1 #define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_XHCI 0x15db #define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_XHCI 0x15d4 #define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_XHCI 0x15e9 @@ -232,6 +233,7 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) if (pdev->vendor == PCI_VENDOR_ID_INTEL && (pdev->device == PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_XHCI || pdev->device == PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_XHCI || pdev->device == PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_XHCI || pdev->device == PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_XHCI || pdev->device == PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_XHCI || From 5a8e3229ac27956bdcc25b2709e5d196d109a27a Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 8 Dec 2020 11:29:11 +0200 Subject: [PATCH 122/172] xhci-pci: Allow host runtime PM as default for Intel Maple Ridge xHCI Intel Maple Ridge is successor of Titan Ridge Thunderbolt controller. As Titan Ridge this one also includes xHCI host controller. In order to safe energy we should put it to low power state by default when idle. For this reason allow host runtime PM for Maple Ridge. Signed-off-by: Mika Westerberg Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20201208092912.1773650-5-mathias.nyman@linux.intel.com Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-pci.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 5f94d7edeb37..84da8406d5b4 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -56,6 +56,7 @@ #define PCI_DEVICE_ID_INTEL_ICE_LAKE_XHCI 0x8a13 #define PCI_DEVICE_ID_INTEL_CML_XHCI 0xa3af #define PCI_DEVICE_ID_INTEL_TIGER_LAKE_XHCI 0x9a13 +#define PCI_DEVICE_ID_INTEL_MAPLE_RIDGE_XHCI 0x1138 #define PCI_DEVICE_ID_AMD_PROMONTORYA_4 0x43b9 #define PCI_DEVICE_ID_AMD_PROMONTORYA_3 0x43ba @@ -240,7 +241,8 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) pdev->device == PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_XHCI || pdev->device == PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_XHCI || pdev->device == PCI_DEVICE_ID_INTEL_ICE_LAKE_XHCI || - pdev->device == PCI_DEVICE_ID_INTEL_TIGER_LAKE_XHCI)) + pdev->device == PCI_DEVICE_ID_INTEL_TIGER_LAKE_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_MAPLE_RIDGE_XHCI)) xhci->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW; if (pdev->vendor == PCI_VENDOR_ID_ETRON && From c1373f10479b624fb6dba0805d673e860f1b421d Mon Sep 17 00:00:00 2001 From: Li Jun Date: Tue, 8 Dec 2020 11:29:12 +0200 Subject: [PATCH 123/172] xhci: Give USB2 ports time to enter U3 in bus suspend If a USB2 device wakeup is not enabled/supported the link state may still be in U0 in xhci_bus_suspend(), where it's then manually put to suspended U3 state. Just as with selective suspend the device needs time to enter U3 suspend before continuing with further suspend operations (e.g. system suspend), otherwise we may enter system suspend with link state in U0. [commit message rewording -Mathias] Cc: Signed-off-by: Li Jun Signed-off-by: Mathias Nyman Link: https://lore.kernel.org/r/20201208092912.1773650-6-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index c799ca5361d4..74c497fd3476 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -1712,6 +1712,10 @@ retry: hcd->state = HC_STATE_SUSPENDED; bus_state->next_statechange = jiffies + msecs_to_jiffies(10); spin_unlock_irqrestore(&xhci->lock, flags); + + if (bus_state->bus_suspended) + usleep_range(5000, 10000); + return 0; } From d6ff32478d7e95d6ca199b5c852710d6964d5811 Mon Sep 17 00:00:00 2001 From: Zhang Qilong Date: Mon, 23 Nov 2020 22:57:19 +0800 Subject: [PATCH 124/172] usb: ehci-omap: Fix PM disable depth umbalance in ehci_hcd_omap_probe The pm_runtime_enable will decrement the power disable depth. Imbalance depth will resulted in enabling runtime PM of device fails later. Thus a pairing decrement must be needed on the error handling path to keep it balanced. Fixes: 6c984b066d84b ("ARM: OMAP: USBHOST: Replace usbhs core driver APIs by Runtime pm APIs") Acked-by: Alan Stern Signed-off-by: Zhang Qilong Link: https://lore.kernel.org/r/20201123145719.1455849-1-zhangqilong3@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-omap.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c index 8771a2ed6926..7f4a03e8647a 100644 --- a/drivers/usb/host/ehci-omap.c +++ b/drivers/usb/host/ehci-omap.c @@ -220,6 +220,7 @@ static int ehci_hcd_omap_probe(struct platform_device *pdev) err_pm_runtime: pm_runtime_put_sync(dev); + pm_runtime_disable(dev); err_phy: for (i = 0; i < omap->nports; i++) { From 3a288efb08543b1368f8d49342e5943f1adf58ea Mon Sep 17 00:00:00 2001 From: Utkarsh Patel Date: Thu, 3 Dec 2020 14:08:13 -0800 Subject: [PATCH 125/172] usb: typec: intel_pmc_mux: Use correct response message bits When Intel PMC Mux agent driver receives the response message from PMC, it checks for the same response bits for all the mux states. Corrected it by checking correct response message bits, Bit 8 & 9 for the SAFE Mode and Alternate Modes and Bit 16 & 17 for the Connect and Disconnect Modes. Reviewed-by: Heikki Krogerus Signed-off-by: Utkarsh Patel Link: https://lore.kernel.org/r/20201203220813.16281-1-utkarsh.h.patel@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux/intel_pmc_mux.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c index aa3211f1c4c3..e58ae8a7fefb 100644 --- a/drivers/usb/typec/mux/intel_pmc_mux.c +++ b/drivers/usb/typec/mux/intel_pmc_mux.c @@ -176,6 +176,7 @@ static int hsl_orientation(struct pmc_usb_port *port) static int pmc_usb_command(struct pmc_usb_port *port, u8 *msg, u32 len) { u8 response[4]; + u8 status_res; int ret; /* @@ -189,9 +190,13 @@ static int pmc_usb_command(struct pmc_usb_port *port, u8 *msg, u32 len) if (ret) return ret; - if (response[2] & PMC_USB_RESP_STATUS_FAILURE) { - if (response[2] & PMC_USB_RESP_STATUS_FATAL) + status_res = (msg[0] & 0xf) < PMC_USB_SAFE_MODE ? + response[2] : response[1]; + + if (status_res & PMC_USB_RESP_STATUS_FAILURE) { + if (status_res & PMC_USB_RESP_STATUS_FATAL) return -EIO; + return -EBUSY; } From e4a9378083c57a22624cda8b780ff5f5da4de7ef Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Tue, 1 Dec 2020 19:17:33 -0800 Subject: [PATCH 126/172] usb: typec: tcpm: Pass down negotiated rev to update retry count nRetryCount was updated from 3 to 2 between PD2.0 and PD3.0 spec. nRetryCount in "Table 6-34 Counter parameters" of the PD 2.0 spec is set to 3, whereas, nRetryCount in "Table 6-59 Counter parameters" is set to 2. Pass down negotiated rev in pd_transmit so that low level chip drivers can update the retry count accordingly before attempting packet transmission. This helps in passing "TEST.PD.PORT.ALL.02" of the "Power Delivery Merged" test suite which was initially failing with "The UUT did not retransmit the message nReryCount times" In fusb302 & tcpci drivers, by default the driver sets the retry count to 3 (Default for PD 2.0). Update this to 2, if the negotiated rev is PD 3.0. In wcove, since the retry count is intentionally set to max, leaving it as is. Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20201202031733.647808-1-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/fusb302.c | 16 +++++++++++----- drivers/usb/typec/tcpm/tcpci.c | 12 +++++++----- drivers/usb/typec/tcpm/tcpm.c | 2 +- drivers/usb/typec/tcpm/wcove.c | 3 ++- include/linux/usb/tcpm.h | 2 +- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index 99562cc65ca6..ebc46b9f776c 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -343,12 +343,11 @@ static int fusb302_sw_reset(struct fusb302_chip *chip) return ret; } -static int fusb302_enable_tx_auto_retries(struct fusb302_chip *chip) +static int fusb302_enable_tx_auto_retries(struct fusb302_chip *chip, u8 retry_count) { int ret = 0; - ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3, - FUSB_REG_CONTROL3_N_RETRIES_3 | + ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3, retry_count | FUSB_REG_CONTROL3_AUTO_RETRY); return ret; @@ -399,7 +398,7 @@ static int tcpm_init(struct tcpc_dev *dev) ret = fusb302_sw_reset(chip); if (ret < 0) return ret; - ret = fusb302_enable_tx_auto_retries(chip); + ret = fusb302_enable_tx_auto_retries(chip, FUSB_REG_CONTROL3_N_RETRIES_3); if (ret < 0) return ret; ret = fusb302_init_interrupt(chip); @@ -1017,7 +1016,7 @@ static const char * const transmit_type_name[] = { }; static int tcpm_pd_transmit(struct tcpc_dev *dev, enum tcpm_transmit_type type, - const struct pd_message *msg) + const struct pd_message *msg, unsigned int negotiated_rev) { struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, tcpc_dev); @@ -1026,6 +1025,13 @@ static int tcpm_pd_transmit(struct tcpc_dev *dev, enum tcpm_transmit_type type, mutex_lock(&chip->lock); switch (type) { case TCPC_TX_SOP: + /* nRetryCount 3 in P2.0 spec, whereas 2 in PD3.0 spec */ + ret = fusb302_enable_tx_auto_retries(chip, negotiated_rev > PD_REV20 ? + FUSB_REG_CONTROL3_N_RETRIES_2 : + FUSB_REG_CONTROL3_N_RETRIES_3); + if (ret < 0) + fusb302_log(chip, "Cannot update retry count ret=%d", ret); + ret = fusb302_pd_send_message(chip, msg); if (ret < 0) fusb302_log(chip, diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index 12d983a75510..98a2455f779f 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -18,7 +18,8 @@ #include "tcpci.h" -#define PD_RETRY_COUNT 3 +#define PD_RETRY_COUNT_DEFAULT 3 +#define PD_RETRY_COUNT_3_0_OR_HIGHER 2 #define AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV 3500 #define AUTO_DISCHARGE_PD_HEADROOM_MV 850 #define AUTO_DISCHARGE_PPS_HEADROOM_MV 1250 @@ -447,9 +448,8 @@ static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink) return 0; } -static int tcpci_pd_transmit(struct tcpc_dev *tcpc, - enum tcpm_transmit_type type, - const struct pd_message *msg) +static int tcpci_pd_transmit(struct tcpc_dev *tcpc, enum tcpm_transmit_type type, + const struct pd_message *msg, unsigned int negotiated_rev) { struct tcpci *tcpci = tcpc_to_tcpci(tcpc); u16 header = msg ? le16_to_cpu(msg->header) : 0; @@ -497,7 +497,9 @@ static int tcpci_pd_transmit(struct tcpc_dev *tcpc, } } - reg = (PD_RETRY_COUNT << TCPC_TRANSMIT_RETRY_SHIFT) | (type << TCPC_TRANSMIT_TYPE_SHIFT); + /* nRetryCount is 3 in PD2.0 spec where 2 in PD3.0 spec */ + reg = ((negotiated_rev > PD_REV20 ? PD_RETRY_COUNT_3_0_OR_HIGHER : PD_RETRY_COUNT_DEFAULT) + << TCPC_TRANSMIT_RETRY_SHIFT) | (type << TCPC_TRANSMIT_TYPE_SHIFT); ret = regmap_write(tcpci->regmap, TCPC_TRANSMIT, reg); if (ret < 0) return ret; diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 3bbc1f10af49..c73bc3a8356a 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -667,7 +667,7 @@ static int tcpm_pd_transmit(struct tcpm_port *port, tcpm_log(port, "PD TX, type: %#x", type); reinit_completion(&port->tx_complete); - ret = port->tcpc->pd_transmit(port->tcpc, type, msg); + ret = port->tcpc->pd_transmit(port->tcpc, type, msg, port->negotiated_rev); if (ret < 0) return ret; diff --git a/drivers/usb/typec/tcpm/wcove.c b/drivers/usb/typec/tcpm/wcove.c index 9b745f432c91..79ae63950050 100644 --- a/drivers/usb/typec/tcpm/wcove.c +++ b/drivers/usb/typec/tcpm/wcove.c @@ -356,7 +356,8 @@ static int wcove_set_pd_rx(struct tcpc_dev *tcpc, bool on) static int wcove_pd_transmit(struct tcpc_dev *tcpc, enum tcpm_transmit_type type, - const struct pd_message *msg) + const struct pd_message *msg, + unsigned int negotiated_rev) { struct wcove_typec *wcove = tcpc_to_wcove(tcpc); unsigned int info = 0; diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index e68aaa12886f..efaedd7e8a18 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -121,7 +121,7 @@ struct tcpc_dev { enum typec_cc_status cc); int (*try_role)(struct tcpc_dev *dev, int role); int (*pd_transmit)(struct tcpc_dev *dev, enum tcpm_transmit_type type, - const struct pd_message *msg); + const struct pd_message *msg, unsigned int negotiated_rev); int (*set_bist_data)(struct tcpc_dev *dev, bool on); int (*enable_frs)(struct tcpc_dev *dev, bool enable); void (*frs_sourcing_vbus)(struct tcpc_dev *dev); From 3bac42f02d41dd85da7afb9ffc3f4539fad20cc5 Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Wed, 2 Dec 2020 19:19:08 -0800 Subject: [PATCH 127/172] usb: typec: tcpm: Clear send_discover in tcpm_check_send_discover tcpm_check_send_discover does not clear the send_discover flag when any of the following conditions are not met. 1. data_role is TYPEC_HOST 2. link is pd_capable Discovery indentity would anyways not be attempted during the current session anymore when the above conditions are not met. Hence clear the send_discover flag here to prevent tcpm_enable_frs_work from rescheduling indefinetly. Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20201203031908.1491542-1-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index c73bc3a8356a..1c64b831bbb5 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -3035,10 +3035,9 @@ static inline enum tcpm_state unattached_state(struct tcpm_port *port) static void tcpm_check_send_discover(struct tcpm_port *port) { if (port->data_role == TYPEC_HOST && port->send_discover && - port->pd_capable) { + port->pd_capable) tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0); - port->send_discover = false; - } + port->send_discover = false; } static void tcpm_swap_complete(struct tcpm_port *port, int result) From 28b43d3d746b89fc112fe681e018b39b43495dad Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Tue, 1 Dec 2020 20:08:38 -0800 Subject: [PATCH 128/172] usb: typec: tcpm: Introduce vsafe0v for vbus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TCPM at present lacks the notion of VSAFE0V. There are three vbus threshold levels that are critical to track: a. vSafe5V - VBUS “5 volts” as defined by the USB PD specification. b. vSinkDisconnect - Threshold used for transition from Attached.SNK to Unattached.SNK. c. vSafe0V - VBUS “0 volts” as defined by the USB PD specification. Tracking vSafe0V is crucial for entry into Try.SNK and Attached.SRC and turning vbus back on by the source in response to hard reset. >From "4.5.2.2.8.2 Exiting from AttachWait.SRC State" section in the Type-C spec: "The port shall transition to Attached.SRC when VBUS is at vSafe0V and the SRC.Rd state is detected on exactly one of the CC1 or CC2 pins for at least tCCDebounce." "A DRP that strongly prefers the Sink role may optionally transition to Try.SNK instead of Attached.SRC when VBUS is at vSafe0V and the SRC.Rd state is detected on exactly one of the CC1 or CC2 pins for at least tCCDebounce." >From "7.1.5 Response to Hard Resets" section in the PD spec: "After establishing the vSafe0V voltage condition on VBUS, the Source Shall wait tSrcRecover before re-applying VCONN and restoring VBUS to vSafe5V." vbus_present in the TCPM code tracks vSafe5V(vbus_present is true) and vSinkDisconnect(vbus_present is false). This change adds is_vbus_vsafe0v callback which when set makes TCPM query for vSafe0V voltage level when needed. Since not all TCPC controllers might have the capability to report vSafe0V, TCPM assumes that vSafe0V is same as vSinkDisconnect when is_vbus_vsafe0v callback is not set. This allows TCPM to continue to support controllers which don't have the support for reporting vSafe0V. Introducing vSafe0V helps fix the failure reported at "Step 15. CVS verifies PUT remains in AttachWait.SRC for 500ms" of "TD 4.7.2 Try. SNK DRP Connect DRP Test" of "Universal Serial Bus Type-C (USB Type-C) Functional Test Specification Chapters 4 and 5". Here the compliance tester intentionally maintains vbus at greater than vSafe0V and expects the Product under test to stay in AttachWait.SRC till vbus drops to vSafe0V. Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20201202040840.663578-1-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 63 +++++++++++++++++++++++++++++------ include/linux/usb/tcpm.h | 7 ++++ 2 files changed, 59 insertions(+), 11 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 1c64b831bbb5..cedc6cf82d61 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -258,7 +258,19 @@ struct tcpm_port { bool attached; bool connected; enum typec_port_type port_type; + + /* + * Set to true when vbus is greater than VSAFE5V min. + * Set to false when vbus falls below vSinkDisconnect max threshold. + */ bool vbus_present; + + /* + * Set to true when vbus is less than VSAFE0V max. + * Set to false when vbus is greater than VSAFE0V max. + */ + bool vbus_vsafe0v; + bool vbus_never_low; bool vbus_source; bool vbus_charge; @@ -3093,7 +3105,7 @@ static void run_state_machine(struct tcpm_port *port) else if (tcpm_port_is_audio(port)) tcpm_set_state(port, AUDIO_ACC_ATTACHED, PD_T_CC_DEBOUNCE); - else if (tcpm_port_is_source(port)) + else if (tcpm_port_is_source(port) && port->vbus_vsafe0v) tcpm_set_state(port, tcpm_try_snk(port) ? SNK_TRY : SRC_ATTACHED, @@ -4096,6 +4108,12 @@ static void _tcpm_pd_vbus_on(struct tcpm_port *port) { tcpm_log_force(port, "VBUS on"); port->vbus_present = true; + /* + * When vbus_present is true i.e. Voltage at VBUS is greater than VSAFE5V implicitly + * states that vbus is not at VSAFE0V, hence clear the vbus_vsafe0v flag here. + */ + port->vbus_vsafe0v = false; + switch (port->state) { case SNK_TRANSITION_SINK_VBUS: port->explicit_contract = true; @@ -4185,16 +4203,8 @@ static void _tcpm_pd_vbus_off(struct tcpm_port *port) case SNK_HARD_RESET_SINK_OFF: tcpm_set_state(port, SNK_HARD_RESET_WAIT_VBUS, 0); break; - case SRC_HARD_RESET_VBUS_OFF: - /* - * After establishing the vSafe0V voltage condition on VBUS, the Source Shall wait - * tSrcRecover before re-applying VCONN and restoring VBUS to vSafe5V. - */ - tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SRC_RECOVER); - break; case HARD_RESET_SEND: break; - case SNK_TRY: /* Do nothing, waiting for timeout */ break; @@ -4265,6 +4275,28 @@ static void _tcpm_pd_vbus_off(struct tcpm_port *port) } } +static void _tcpm_pd_vbus_vsafe0v(struct tcpm_port *port) +{ + tcpm_log_force(port, "VBUS VSAFE0V"); + port->vbus_vsafe0v = true; + switch (port->state) { + case SRC_HARD_RESET_VBUS_OFF: + /* + * After establishing the vSafe0V voltage condition on VBUS, the Source Shall wait + * tSrcRecover before re-applying VCONN and restoring VBUS to vSafe5V. + */ + tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SRC_RECOVER); + break; + case SRC_ATTACH_WAIT: + if (tcpm_port_is_source(port)) + tcpm_set_state(port, tcpm_try_snk(port) ? SNK_TRY : SRC_ATTACHED, + PD_T_CC_DEBOUNCE); + break; + default: + break; + } +} + static void _tcpm_pd_hard_reset(struct tcpm_port *port) { tcpm_log_force(port, "Received hard reset"); @@ -4300,10 +4332,19 @@ static void tcpm_pd_event_handler(struct kthread_work *work) bool vbus; vbus = port->tcpc->get_vbus(port->tcpc); - if (vbus) + if (vbus) { _tcpm_pd_vbus_on(port); - else + } else { _tcpm_pd_vbus_off(port); + /* + * When TCPC does not support detecting vsafe0v voltage level, + * treat vbus absent as vsafe0v. Else invoke is_vbus_vsafe0v + * to see if vbus has discharge to VSAFE0V. + */ + if (!port->tcpc->is_vbus_vsafe0v || + port->tcpc->is_vbus_vsafe0v(port->tcpc)) + _tcpm_pd_vbus_vsafe0v(port); + } } if (events & TCPM_CC_EVENT) { enum typec_cc_status cc1, cc2; diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index efaedd7e8a18..f4a18427f5c4 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -98,6 +98,12 @@ enum tcpm_transmit_type { * will be turned on. requested_vbus_voltage is set to 0 when vbus * is going to disappear knowingly i.e. during PR_SWAP and * HARD_RESET etc. + * @is_vbus_vsafe0v: + * Optional; TCPCI spec based TCPC implementations are expected to + * detect VSAFE0V voltage level at vbus. When detection of VSAFE0V + * is supported by TCPC, set this callback for TCPM to query + * whether vbus is at VSAFE0V when needed. + * Returns true when vbus is at VSAFE0V, false otherwise. */ struct tcpc_dev { struct fwnode_handle *fwnode; @@ -128,6 +134,7 @@ struct tcpc_dev { int (*enable_auto_vbus_discharge)(struct tcpc_dev *dev, bool enable); int (*set_auto_vbus_discharge_threshold)(struct tcpc_dev *dev, enum typec_pwr_opmode mode, bool pps_active, u32 requested_vbus_voltage); + bool (*is_vbus_vsafe0v)(struct tcpc_dev *dev); }; struct tcpm_port; From 766c485b86ef52765926b69a1145cadcf2db1e7d Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Tue, 1 Dec 2020 20:08:39 -0800 Subject: [PATCH 129/172] usb: typec: tcpci: Add support to report vSafe0V This change adds vbus_vsafe0v which when set, makes TCPM query for VSAFE0V by assigning the tcpc.is_vbus_vsafe0v callback. Also enables ALERT.ExtendedStatus which is triggered when status of EXTENDED_STATUS.vSafe0V changes. EXTENDED_STATUS.vSafe0V is set when vbus is at vSafe0V and cleared otherwise. Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20201202040840.663578-2-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci.c | 39 +++++++++++++++++++++++++++++----- drivers/usb/typec/tcpm/tcpci.h | 6 ++++++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index 98a2455f779f..af5524338a63 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -403,6 +403,19 @@ static int tcpci_get_vbus(struct tcpc_dev *tcpc) return !!(reg & TCPC_POWER_STATUS_VBUS_PRES); } +static bool tcpci_is_vbus_vsafe0v(struct tcpc_dev *tcpc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg; + int ret; + + ret = regmap_read(tcpci->regmap, TCPC_EXTENDED_STATUS, ®); + if (ret < 0) + return false; + + return !!(reg & TCPC_EXTENDED_STATUS_VSAFE0V); +} + static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink) { struct tcpci *tcpci = tcpc_to_tcpci(tcpc); @@ -556,12 +569,22 @@ static int tcpci_init(struct tcpc_dev *tcpc) TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS; if (tcpci->controls_vbus) reg |= TCPC_ALERT_POWER_STATUS; + /* Enable VSAFE0V status interrupt when detecting VSAFE0V is supported */ + if (tcpci->data->vbus_vsafe0v) { + reg |= TCPC_ALERT_EXTENDED_STATUS; + ret = regmap_write(tcpci->regmap, TCPC_EXTENDED_STATUS_MASK, + TCPC_EXTENDED_STATUS_VSAFE0V); + if (ret < 0) + return ret; + } return tcpci_write16(tcpci, TCPC_ALERT_MASK, reg); } irqreturn_t tcpci_irq(struct tcpci *tcpci) { u16 status; + int ret; + unsigned int raw; tcpci_read16(tcpci, TCPC_ALERT, &status); @@ -577,15 +600,12 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci) tcpm_cc_change(tcpci->port); if (status & TCPC_ALERT_POWER_STATUS) { - unsigned int reg; - - regmap_read(tcpci->regmap, TCPC_POWER_STATUS_MASK, ®); - + regmap_read(tcpci->regmap, TCPC_POWER_STATUS_MASK, &raw); /* * If power status mask has been reset, then the TCPC * has reset. */ - if (reg == 0xff) + if (raw == 0xff) tcpm_tcpc_reset(tcpci->port); else tcpm_vbus_change(tcpci->port); @@ -624,6 +644,12 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci) tcpm_pd_receive(tcpci->port, &msg); } + if (status & TCPC_ALERT_EXTENDED_STATUS) { + ret = regmap_read(tcpci->regmap, TCPC_EXTENDED_STATUS, &raw); + if (!ret && (raw & TCPC_EXTENDED_STATUS_VSAFE0V)) + tcpm_vbus_change(tcpci->port); + } + if (status & TCPC_ALERT_RX_HARD_RST) tcpm_pd_hard_reset(tcpci->port); @@ -701,6 +727,9 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) tcpci_set_auto_vbus_discharge_threshold; } + if (tcpci->data->vbus_vsafe0v) + tcpci->tcpc.is_vbus_vsafe0v = tcpci_is_vbus_vsafe0v; + err = tcpci_parse_config(tcpci); if (err < 0) return ERR_PTR(err); diff --git a/drivers/usb/typec/tcpm/tcpci.h b/drivers/usb/typec/tcpm/tcpci.h index 3fe313655f0c..116a69c85e38 100644 --- a/drivers/usb/typec/tcpm/tcpci.h +++ b/drivers/usb/typec/tcpm/tcpci.h @@ -49,6 +49,9 @@ #define TCPC_TCPC_CTRL_ORIENTATION BIT(0) #define TCPC_TCPC_CTRL_BIST_TM BIT(1) +#define TCPC_EXTENDED_STATUS 0x20 +#define TCPC_EXTENDED_STATUS_VSAFE0V BIT(0) + #define TCPC_ROLE_CTRL 0x1a #define TCPC_ROLE_CTRL_DRP BIT(6) #define TCPC_ROLE_CTRL_RP_VAL_SHIFT 4 @@ -155,11 +158,14 @@ struct tcpci; * is sourcing vbus. * @auto_discharge_disconnect: * Optional; Enables TCPC to autonously discharge vbus on disconnect. + * @vbus_vsafe0v: + * optional; Set when TCPC can detect whether vbus is at VSAFE0V. */ struct tcpci_data { struct regmap *regmap; unsigned char TX_BUF_BYTE_x_hidden:1; unsigned char auto_discharge_disconnect:1; + unsigned char vbus_vsafe0v:1; int (*init)(struct tcpci *tcpci, struct tcpci_data *data); int (*set_vconn)(struct tcpci *tcpci, struct tcpci_data *data, From 0fbb7d06d3783ad9dac063bf2c956f176d57e657 Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Tue, 1 Dec 2020 20:08:40 -0800 Subject: [PATCH 130/172] usb: typec: tcpci_maxim: Enable VSAFE0V signalling Unmask EXTENDED_STATUS_MASK.vSafe0V, ALERT.Extended_Status and set vbus_vsafe0v to enable VSAFE0V signalling. Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20201202040840.663578-3-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci_maxim.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.c b/drivers/usb/typec/tcpm/tcpci_maxim.c index c1797239bf08..319266329b42 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim.c @@ -112,11 +112,18 @@ static void max_tcpci_init_regs(struct max_tcpci_chip *chip) return; } + /* Enable VSAFE0V detection */ + ret = max_tcpci_write8(chip, TCPC_EXTENDED_STATUS_MASK, TCPC_EXTENDED_STATUS_VSAFE0V); + if (ret < 0) { + dev_err(chip->dev, "Unable to unmask TCPC_EXTENDED_STATUS_VSAFE0V ret:%d\n", ret); + return; + } + alert_mask = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_TX_FAILED | TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_RX_STATUS | TCPC_ALERT_CC_STATUS | TCPC_ALERT_VBUS_DISCNCT | TCPC_ALERT_RX_BUF_OVF | TCPC_ALERT_POWER_STATUS | /* Enable Extended alert for detecting Fast Role Swap Signal */ - TCPC_ALERT_EXTND; + TCPC_ALERT_EXTND | TCPC_ALERT_EXTENDED_STATUS; ret = max_tcpci_write16(chip, TCPC_ALERT_MASK, alert_mask); if (ret < 0) { @@ -315,6 +322,12 @@ static irqreturn_t _max_tcpci_irq(struct max_tcpci_chip *chip, u16 status) } } + if (status & TCPC_ALERT_EXTENDED_STATUS) { + ret = max_tcpci_read8(chip, TCPC_EXTENDED_STATUS, (u8 *)®_status); + if (ret >= 0 && (reg_status & TCPC_EXTENDED_STATUS_VSAFE0V)) + tcpm_vbus_change(chip->port); + } + if (status & TCPC_ALERT_RX_STATUS) process_rx(chip, status); @@ -442,6 +455,7 @@ static int max_tcpci_probe(struct i2c_client *client, const struct i2c_device_id chip->data.init = tcpci_init; chip->data.frs_sourcing_vbus = max_tcpci_frs_sourcing_vbus; chip->data.auto_discharge_disconnect = true; + chip->data.vbus_vsafe0v = true; max_tcpci_init_regs(chip); chip->tcpci = tcpci_register_port(chip->dev, &chip->data); From a251963f76fa0226d0fdf0c4f989496f18d9ae7f Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 9 Dec 2020 11:42:21 +0100 Subject: [PATCH 131/172] USB: serial: option: add interface-number sanity check to flag handling Add an interface-number sanity check before testing the device flags to avoid relying on undefined behaviour when left shifting in case a device uses an interface number greater than or equal to BITS_PER_LONG (i.e. 64 or 32). Reported-by: syzbot+8881b478dad0a7971f79@syzkaller.appspotmail.com Fixes: c3a65808f04a ("USB: serial: option: reimplement interface masking") Cc: stable@vger.kernel.org Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 2a3bfd6f867e..c5908c4f2046 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -561,6 +561,9 @@ static void option_instat_callback(struct urb *urb); /* Device flags */ +/* Highest interface number which can be used with NCTRL() and RSVD() */ +#define FLAG_IFNUM_MAX 7 + /* Interface does not support modem-control requests */ #define NCTRL(ifnum) ((BIT(ifnum) & 0xff) << 8) @@ -2089,6 +2092,14 @@ static struct usb_serial_driver * const serial_drivers[] = { module_usb_serial_driver(serial_drivers, option_ids); +static bool iface_is_reserved(unsigned long device_flags, u8 ifnum) +{ + if (ifnum > FLAG_IFNUM_MAX) + return false; + + return device_flags & RSVD(ifnum); +} + static int option_probe(struct usb_serial *serial, const struct usb_device_id *id) { @@ -2105,7 +2116,7 @@ static int option_probe(struct usb_serial *serial, * the same class/subclass/protocol as the serial interfaces. Look at * the Windows driver .INF files for reserved interface numbers. */ - if (device_flags & RSVD(iface_desc->bInterfaceNumber)) + if (iface_is_reserved(device_flags, iface_desc->bInterfaceNumber)) return -ENODEV; /* @@ -2121,6 +2132,14 @@ static int option_probe(struct usb_serial *serial, return 0; } +static bool iface_no_modem_control(unsigned long device_flags, u8 ifnum) +{ + if (ifnum > FLAG_IFNUM_MAX) + return false; + + return device_flags & NCTRL(ifnum); +} + static int option_attach(struct usb_serial *serial) { struct usb_interface_descriptor *iface_desc; @@ -2136,7 +2155,7 @@ static int option_attach(struct usb_serial *serial) iface_desc = &serial->interface->cur_altsetting->desc; - if (!(device_flags & NCTRL(iface_desc->bInterfaceNumber))) + if (!iface_no_modem_control(device_flags, iface_desc->bInterfaceNumber)) data->use_send_setup = 1; if (device_flags & ZLP) From 3291eb7329b1c999f952f7d84f9d716b65d235cf Mon Sep 17 00:00:00 2001 From: "Enrico Weigelt, metux IT consult" Date: Tue, 8 Dec 2020 10:32:04 +0100 Subject: [PATCH 132/172] drivers: usb: atm: reduce noise If drivers work correctly, they should remain silent. Signed-off-by: Enrico Weigelt, metux IT consult Link: https://lore.kernel.org/r/20201208093206.24780-1-info@metux.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/atm/cxacru.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/usb/atm/cxacru.c b/drivers/usb/atm/cxacru.c index e62a770a5d3b..0114cc54e622 100644 --- a/drivers/usb/atm/cxacru.c +++ b/drivers/usb/atm/cxacru.c @@ -810,9 +810,6 @@ static int cxacru_atm_start(struct usbatm_data *usbatm_instance, mutex_unlock(&instance->poll_state_serialize); mutex_unlock(&instance->adsl_state_serialize); - printk(KERN_INFO "%s%d: %s %pM\n", atm_dev->type, atm_dev->number, - usbatm_instance->description, atm_dev->esi); - if (start_polling) cxacru_poll_status(&instance->poll_work.work); return 0; From 6d4e3866b7656268a04f518a7feb2c4698d8b899 Mon Sep 17 00:00:00 2001 From: "Enrico Weigelt, metux IT consult" Date: Tue, 8 Dec 2020 10:32:05 +0100 Subject: [PATCH 133/172] drivers: usb: atm: use atm_info() instead of atm_printk(KERN_INFO ... Since we already have the useful atm_info() macro, use it instead of raw atm_printk() Signed-off-by: Enrico Weigelt, metux IT consult Link: https://lore.kernel.org/r/20201208093206.24780-2-info@metux.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/atm/cxacru.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/atm/cxacru.c b/drivers/usb/atm/cxacru.c index 0114cc54e622..4d3947476f0e 100644 --- a/drivers/usb/atm/cxacru.c +++ b/drivers/usb/atm/cxacru.c @@ -849,15 +849,15 @@ static void cxacru_poll_status(struct work_struct *work) switch (instance->adsl_status) { case 0: - atm_printk(KERN_INFO, usbatm, "ADSL state: running\n"); + atm_info(usbatm, "ADSL state: running\n"); break; case 1: - atm_printk(KERN_INFO, usbatm, "ADSL state: stopped\n"); + atm_info(usbatm, "ADSL state: stopped\n"); break; default: - atm_printk(KERN_INFO, usbatm, "Unknown adsl status %02x\n", instance->adsl_status); + atm_info(usbatm, "Unknown adsl status %02x\n", instance->adsl_status); break; } } From ba7052f52c13a6feeced5adbb906b6b7ef7efab8 Mon Sep 17 00:00:00 2001 From: "Enrico Weigelt, metux IT consult" Date: Tue, 8 Dec 2020 10:32:06 +0100 Subject: [PATCH 134/172] drivers: usb: atm: use pr_err() and pr_warn() instead of raw printk() Since we have the nice helpers pr_err() and pr_warn(), use them instead of raw printk(). Acked-by: Duncan Sands Signed-off-by: Enrico Weigelt, metux IT consult Link: https://lore.kernel.org/r/20201208093206.24780-3-info@metux.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/atm/usbatm.c | 2 +- drivers/usb/atm/xusbatm.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/atm/usbatm.c b/drivers/usb/atm/usbatm.c index f49792f951ab..33ae03ac13a6 100644 --- a/drivers/usb/atm/usbatm.c +++ b/drivers/usb/atm/usbatm.c @@ -1278,7 +1278,7 @@ EXPORT_SYMBOL_GPL(usbatm_usb_disconnect); static int __init usbatm_usb_init(void) { if (sizeof(struct usbatm_control) > sizeof_field(struct sk_buff, cb)) { - printk(KERN_ERR "%s unusable with this kernel!\n", usbatm_driver_name); + pr_err("%s unusable with this kernel!\n", usbatm_driver_name); return -EIO; } diff --git a/drivers/usb/atm/xusbatm.c b/drivers/usb/atm/xusbatm.c index ffc9810070a3..0befbf63d1cc 100644 --- a/drivers/usb/atm/xusbatm.c +++ b/drivers/usb/atm/xusbatm.c @@ -179,7 +179,7 @@ static int __init xusbatm_init(void) num_vendor != num_product || num_vendor != num_rx_endpoint || num_vendor != num_tx_endpoint) { - printk(KERN_WARNING "xusbatm: malformed module parameters\n"); + pr_warn("xusbatm: malformed module parameters\n"); return -EINVAL; } From 44ef9b2cd9f8ad30db3e9c87fe0a0d7b70449237 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 26 Nov 2020 22:37:04 +0000 Subject: [PATCH 135/172] usb: phy: Fix spelling mistake in Kconfig help text There is a spelling mistake in the Kconfig help text. Fix it. Signed-off-by: Colin Ian King Link: https://lore.kernel.org/r/20201126223704.13273-1-colin.king@canonical.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index ef4787cd3d37..52eebcb88c1f 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -144,7 +144,7 @@ config USB_MV_OTG depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y' select USB_PHY help - Say Y here if you want to build Marvell USB OTG transciever + Say Y here if you want to build Marvell USB OTG transceiver driver in kernel (including PXA and MMP series). This driver implements role switch between EHCI host driver and gadget driver. From 21f5b2fb5fab893e2c745e4e24c4f425cc3db4c1 Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Fri, 27 Nov 2020 11:03:36 -0800 Subject: [PATCH 136/172] USB: host: u123-hcd: remove trailing semicolon in macro definition The macro use will already have a semicolon. Signed-off-by: Tom Rix Link: https://lore.kernel.org/r/20201127190336.2841413-1-trix@redhat.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/u132-hcd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/host/u132-hcd.c b/drivers/usb/host/u132-hcd.c index 995bc52d2d22..eb96e1e15b71 100644 --- a/drivers/usb/host/u132-hcd.c +++ b/drivers/usb/host/u132-hcd.c @@ -208,13 +208,13 @@ struct u132 { #define ftdi_read_pcimem(pdev, member, data) usb_ftdi_elan_read_pcimem(pdev, \ offsetof(struct ohci_regs, member), 0, data); #define ftdi_write_pcimem(pdev, member, data) usb_ftdi_elan_write_pcimem(pdev, \ - offsetof(struct ohci_regs, member), 0, data); + offsetof(struct ohci_regs, member), 0, data) #define u132_read_pcimem(u132, member, data) \ usb_ftdi_elan_read_pcimem(u132->platform_dev, offsetof(struct \ - ohci_regs, member), 0, data); + ohci_regs, member), 0, data) #define u132_write_pcimem(u132, member, data) \ usb_ftdi_elan_write_pcimem(u132->platform_dev, offsetof(struct \ - ohci_regs, member), 0, data); + ohci_regs, member), 0, data) static inline struct u132 *udev_to_u132(struct u132_udev *udev) { u8 udev_number = udev->udev_number; From 3b78ef0da32f3288c292b2ecf680c31b4674b50d Mon Sep 17 00:00:00 2001 From: Xu Wang Date: Fri, 20 Nov 2020 08:30:54 +0000 Subject: [PATCH 137/172] usb: fotg210-hcd: remove casting dma_alloc_coherent Remove casting the values returned by dma_alloc_coherent. Signed-off-by: Xu Wang Link: https://lore.kernel.org/r/20201120083054.8973-1-vulab@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/fotg210-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/fotg210-hcd.c b/drivers/usb/host/fotg210-hcd.c index 0451943f0bc4..5617ef30530a 100644 --- a/drivers/usb/host/fotg210-hcd.c +++ b/drivers/usb/host/fotg210-hcd.c @@ -1951,7 +1951,7 @@ static int fotg210_mem_init(struct fotg210_hcd *fotg210, gfp_t flags) goto fail; /* Hardware periodic table */ - fotg210->periodic = (__le32 *) + fotg210->periodic = dma_alloc_coherent(fotg210_to_hcd(fotg210)->self.controller, fotg210->periodic_size * sizeof(__le32), &fotg210->periodic_dma, 0); From e5548b05631ec3e6bfdaef1cad28c799545b791b Mon Sep 17 00:00:00 2001 From: Zhang Qilong Date: Mon, 23 Nov 2020 22:58:09 +0800 Subject: [PATCH 138/172] usb: oxu210hp-hcd: Fix memory leak in oxu_create usb_create_hcd will alloc memory for hcd, and we should call usb_put_hcd to free it when adding fails to prevent memory leak. Fixes: b92a78e582b1a ("usb host: Oxford OXU210HP HCD driver") Reported-by: Hulk Robot Signed-off-by: Zhang Qilong Link: https://lore.kernel.org/r/20201123145809.1456541-1-zhangqilong3@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/oxu210hp-hcd.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index aa42df39e6a1..4300326b3730 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -4152,8 +4152,10 @@ static struct usb_hcd *oxu_create(struct platform_device *pdev, oxu->is_otg = otg; ret = usb_add_hcd(hcd, irq, IRQF_SHARED); - if (ret < 0) + if (ret < 0) { + usb_put_hcd(hcd); return ERR_PTR(ret); + } device_wakeup_enable(hcd->self.controller); return hcd; From 11fb08cffbebcbda76b3d882c19398c7571ab355 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 4 Dec 2020 16:47:37 +0000 Subject: [PATCH 139/172] USB: serial: ftdi_sio: report the valid GPIO lines to gpiolib Since it is pretty common for only some of the CBUS lines to be valid as GPIO lines, let's report such validity to the rest of the kernel. Signed-off-by: Marc Zyngier Link: https://lore.kernel.org/r/20201204164739.781812-3-maz@kernel.org Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Signed-off-by: Johan Hovold --- drivers/usb/serial/ftdi_sio.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index e0f4c3d9649c..13e575f16bcd 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -2002,6 +2002,19 @@ static int ftdi_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio, return result; } +static int ftdi_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + struct ftdi_private *priv = usb_get_serial_port_data(port); + unsigned long map = priv->gpio_altfunc; + + bitmap_complement(valid_mask, &map, ngpios); + + return 0; +} + static int ftdi_read_eeprom(struct usb_serial *serial, void *dst, u16 addr, u16 nbytes) { @@ -2173,6 +2186,7 @@ static int ftdi_gpio_init(struct usb_serial_port *port) priv->gc.get_direction = ftdi_gpio_direction_get; priv->gc.direction_input = ftdi_gpio_direction_input; priv->gc.direction_output = ftdi_gpio_direction_output; + priv->gc.init_valid_mask = ftdi_gpio_init_valid_mask; priv->gc.get = ftdi_gpio_get; priv->gc.set = ftdi_gpio_set; priv->gc.get_multiple = ftdi_gpio_get_multiple; From 5d47c887cceed5261e82e2cc7e996b877d5381f8 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 4 Dec 2020 16:47:39 +0000 Subject: [PATCH 140/172] USB: serial: ftdi_sio: drop GPIO line checking dead code Now that gpiolib can track the validity of GPIO pins, there is no need to check whether the line is valid in request(). Signed-off-by: Marc Zyngier Link: https://lore.kernel.org/r/20201204164739.781812-5-maz@kernel.org Reviewed-by: Andy Shevchenko [johan: amend commit message] Signed-off-by: Johan Hovold --- drivers/usb/serial/ftdi_sio.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 13e575f16bcd..a41ce9e60b96 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -1841,9 +1841,6 @@ static int ftdi_gpio_request(struct gpio_chip *gc, unsigned int offset) struct ftdi_private *priv = usb_get_serial_port_data(port); int result; - if (priv->gpio_altfunc & BIT(offset)) - return -ENODEV; - mutex_lock(&priv->gpio_lock); if (!priv->gpio_used) { /* Set default pin states, as we cannot get them from device */ From fddd408ad448efc49c67f8dfdc4e86b31c683a0c Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 4 Dec 2020 16:47:38 +0000 Subject: [PATCH 141/172] USB: serial: ftdi_sio: log the CBUS GPIO validity The validity of the ftdi CBUS GPIO is pretty hidden so far, and finding out *why* some GPIOs don't work is sometimes hard to identify. So let's help the user by displaying the map of the CBUS pins that are valid for a GPIO. Suggested-by: Linus Walleij Signed-off-by: Marc Zyngier Link: https://lore.kernel.org/r/20201204164739.781812-4-maz@kernel.org Reviewed-by: Andy Shevchenko [johan: demote to KERN_DEBUG, rephrase messages, drop ftx-prog warning] Signed-off-by: Johan Hovold --- drivers/usb/serial/ftdi_sio.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index a41ce9e60b96..94398f89e600 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -2009,6 +2009,12 @@ static int ftdi_gpio_init_valid_mask(struct gpio_chip *gc, bitmap_complement(valid_mask, &map, ngpios); + if (bitmap_empty(valid_mask, ngpios)) + dev_dbg(&port->dev, "no CBUS pin configured for GPIO\n"); + else + dev_dbg(&port->dev, "CBUS%*pbl configured for GPIO\n", ngpios, + valid_mask); + return 0; } From 8010622c86ca5bb44bc98492f5968726fc7c7a21 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Wed, 9 Dec 2020 16:26:39 +0100 Subject: [PATCH 142/172] USB: UAS: introduce a quirk to set no_write_same UAS does not share the pessimistic assumption storage is making that devices cannot deal with WRITE_SAME. A few devices supported by UAS, are reported to not deal well with WRITE_SAME. Those need a quirk. Add it to the device that needs it. Reported-by: David C. Partridge Signed-off-by: Oliver Neukum Cc: stable Link: https://lore.kernel.org/r/20201209152639.9195-1-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- Documentation/admin-guide/kernel-parameters.txt | 1 + drivers/usb/storage/uas.c | 3 +++ drivers/usb/storage/unusual_uas.h | 7 +++++-- drivers/usb/storage/usb.c | 3 +++ include/linux/usb_usual.h | 2 ++ 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 44fde25bb221..f6a1513dfb76 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -5663,6 +5663,7 @@ device); j = NO_REPORT_LUNS (don't use report luns command, uas only); + k = NO_SAME (do not use WRITE_SAME, uas only) l = NOT_LOCKABLE (don't try to lock and unlock ejectable media, not on uas); m = MAX_SECTORS_64 (don't transfer more diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 56422c4b4ff3..bef89c6bd1d7 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -868,6 +868,9 @@ static int uas_slave_configure(struct scsi_device *sdev) if (devinfo->flags & US_FL_NO_READ_CAPACITY_16) sdev->no_read_capacity_16 = 1; + /* Some disks cannot handle WRITE_SAME */ + if (devinfo->flags & US_FL_NO_SAME) + sdev->no_write_same = 1; /* * Some disks return the total number of blocks in response * to READ CAPACITY rather than the highest block number. diff --git a/drivers/usb/storage/unusual_uas.h b/drivers/usb/storage/unusual_uas.h index 711ab240058c..870e9cf3d5dc 100644 --- a/drivers/usb/storage/unusual_uas.h +++ b/drivers/usb/storage/unusual_uas.h @@ -35,12 +35,15 @@ UNUSUAL_DEV(0x054c, 0x087d, 0x0000, 0x9999, USB_SC_DEVICE, USB_PR_DEVICE, NULL, US_FL_NO_REPORT_OPCODES), -/* Reported-by: Julian Groß */ +/* + * Initially Reported-by: Julian Groß + * Further reports David C. Partridge + */ UNUSUAL_DEV(0x059f, 0x105f, 0x0000, 0x9999, "LaCie", "2Big Quadra USB3", USB_SC_DEVICE, USB_PR_DEVICE, NULL, - US_FL_NO_REPORT_OPCODES), + US_FL_NO_REPORT_OPCODES | US_FL_NO_SAME), /* * Apricorn USB3 dongle sometimes returns "USBSUSBSUSBS" in response to SCSI diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c index 94a64729dc27..90aa9c12ffac 100644 --- a/drivers/usb/storage/usb.c +++ b/drivers/usb/storage/usb.c @@ -541,6 +541,9 @@ void usb_stor_adjust_quirks(struct usb_device *udev, unsigned long *fflags) case 'j': f |= US_FL_NO_REPORT_LUNS; break; + case 'k': + f |= US_FL_NO_SAME; + break; case 'l': f |= US_FL_NOT_LOCKABLE; break; diff --git a/include/linux/usb_usual.h b/include/linux/usb_usual.h index 4a19ac3f24d0..6b03fdd69d27 100644 --- a/include/linux/usb_usual.h +++ b/include/linux/usb_usual.h @@ -84,6 +84,8 @@ /* Cannot handle REPORT_LUNS */ \ US_FLAG(ALWAYS_SYNC, 0x20000000) \ /* lies about caching, so always sync */ \ + US_FLAG(NO_SAME, 0x40000000) \ + /* Cannot handle WRITE_SAME */ \ #define US_FLAG(name, value) US_FL_##name = value , enum { US_DO_ALL_FLAGS }; From b3b4a9d70fec50c90c9ee1da408793b50f78a500 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Wed, 7 Oct 2020 06:02:30 +0200 Subject: [PATCH 143/172] usb: cdns3: Add static to cdns3_gadget_exit function Function cdns3_gadget_exit is used only in gadget.c file. This patch removes declaration and definition of this function from gadget-export.h file and makes it static. Signed-off-by: Pawel Laszczak Acked-by: Roger Quadros Signed-off-by: Peter Chen --- drivers/usb/cdns3/gadget-export.h | 3 --- drivers/usb/cdns3/gadget.c | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/usb/cdns3/gadget-export.h b/drivers/usb/cdns3/gadget-export.h index 577469eee961..702c5a267a92 100644 --- a/drivers/usb/cdns3/gadget-export.h +++ b/drivers/usb/cdns3/gadget-export.h @@ -13,7 +13,6 @@ #ifdef CONFIG_USB_CDNS3_GADGET int cdns3_gadget_init(struct cdns3 *cdns); -void cdns3_gadget_exit(struct cdns3 *cdns); #else static inline int cdns3_gadget_init(struct cdns3 *cdns) @@ -21,8 +20,6 @@ static inline int cdns3_gadget_init(struct cdns3 *cdns) return -ENXIO; } -static inline void cdns3_gadget_exit(struct cdns3 *cdns) { } - #endif #endif /* __LINUX_CDNS3_GADGET_EXPORT */ diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c index 365f30fb1159..c62e09ba9231 100644 --- a/drivers/usb/cdns3/gadget.c +++ b/drivers/usb/cdns3/gadget.c @@ -3083,7 +3083,7 @@ static void cdns3_gadget_release(struct device *dev) kfree(priv_dev); } -void cdns3_gadget_exit(struct cdns3 *cdns) +static void cdns3_gadget_exit(struct cdns3 *cdns) { struct cdns3_device *priv_dev; From cdd3013dcc5cf6b03150caac0b96212ab221cf7a Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Thu, 15 Oct 2020 06:54:47 +0200 Subject: [PATCH 144/172] usb: cdns3: Rids of duplicate error message On failure, the platform_get_irq_byname prints an error message, so patch removes error message related to this function from core.c file. A change was suggested during reviewing CDNSP driver by Chunfeng Yun. Acked-by: Roger Quadros Signed-off-by: Pawel Laszczak Acked-by: Peter Chen Signed-off-by: Peter Chen --- drivers/usb/cdns3/core.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index a0f73d4711ae..f2dedce3a40e 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -466,11 +466,8 @@ static int cdns3_probe(struct platform_device *pdev) cdns->xhci_res[1] = *res; cdns->dev_irq = platform_get_irq_byname(pdev, "peripheral"); - if (cdns->dev_irq == -EPROBE_DEFER) - return cdns->dev_irq; - if (cdns->dev_irq < 0) - dev_err(dev, "couldn't get peripheral irq\n"); + return cdns->dev_irq; regs = devm_platform_ioremap_resource_byname(pdev, "dev"); if (IS_ERR(regs)) @@ -478,14 +475,9 @@ static int cdns3_probe(struct platform_device *pdev) cdns->dev_regs = regs; cdns->otg_irq = platform_get_irq_byname(pdev, "otg"); - if (cdns->otg_irq == -EPROBE_DEFER) + if (cdns->otg_irq < 0) return cdns->otg_irq; - if (cdns->otg_irq < 0) { - dev_err(dev, "couldn't get otg irq\n"); - return cdns->otg_irq; - } - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg"); if (!res) { dev_err(dev, "couldn't get otg resource\n"); From ed22764847e8100f0af9af91ccfa58e5c559bd47 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Fri, 22 May 2020 17:56:30 +0800 Subject: [PATCH 145/172] usb: cdns3: host: add .suspend_quirk for xhci-plat.c cdns3 has some special PM sequence between xhci_bus_suspend and xhci_suspend, add quirk to implement it. Reviewed-by: Pawel Laszczak Signed-off-by: Peter Chen --- drivers/usb/cdns3/host-export.h | 6 +++++ drivers/usb/cdns3/host.c | 43 +++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/drivers/usb/cdns3/host-export.h b/drivers/usb/cdns3/host-export.h index ae11810f8826..26041718a086 100644 --- a/drivers/usb/cdns3/host-export.h +++ b/drivers/usb/cdns3/host-export.h @@ -9,9 +9,11 @@ #ifndef __LINUX_CDNS3_HOST_EXPORT #define __LINUX_CDNS3_HOST_EXPORT +struct usb_hcd; #ifdef CONFIG_USB_CDNS3_HOST int cdns3_host_init(struct cdns3 *cdns); +int xhci_cdns3_suspend_quirk(struct usb_hcd *hcd); #else @@ -21,6 +23,10 @@ static inline int cdns3_host_init(struct cdns3 *cdns) } static inline void cdns3_host_exit(struct cdns3 *cdns) { } +static inline int xhci_cdns3_suspend_quirk(struct usb_hcd *hcd) +{ + return 0; +} #endif /* CONFIG_USB_CDNS3_HOST */ diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c index b3e2cb69762c..de8da737fa25 100644 --- a/drivers/usb/cdns3/host.c +++ b/drivers/usb/cdns3/host.c @@ -14,6 +14,18 @@ #include "drd.h" #include "host-export.h" #include +#include "../host/xhci.h" +#include "../host/xhci-plat.h" + +#define XECP_PORT_CAP_REG 0x8000 +#define XECP_AUX_CTRL_REG1 0x8120 + +#define CFG_RXDET_P3_EN BIT(15) +#define LPM_2_STB_SWITCH_EN BIT(25) + +static const struct xhci_plat_priv xhci_plat_cdns3_xhci = { + .suspend_quirk = xhci_cdns3_suspend_quirk, +}; static int __cdns3_host_init(struct cdns3 *cdns) { @@ -39,6 +51,11 @@ static int __cdns3_host_init(struct cdns3 *cdns) goto err1; } + ret = platform_device_add_data(xhci, &xhci_plat_cdns3_xhci, + sizeof(struct xhci_plat_priv)); + if (ret) + goto err1; + ret = platform_device_add(xhci); if (ret) { dev_err(cdns->dev, "failed to register xHCI device\n"); @@ -56,6 +73,32 @@ err1: return ret; } +int xhci_cdns3_suspend_quirk(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + u32 value; + + if (pm_runtime_status_suspended(hcd->self.controller)) + return 0; + + /* set usbcmd.EU3S */ + value = readl(&xhci->op_regs->command); + value |= CMD_PM_INDEX; + writel(value, &xhci->op_regs->command); + + if (hcd->regs) { + value = readl(hcd->regs + XECP_AUX_CTRL_REG1); + value |= CFG_RXDET_P3_EN; + writel(value, hcd->regs + XECP_AUX_CTRL_REG1); + + value = readl(hcd->regs + XECP_PORT_CAP_REG); + value |= LPM_2_STB_SWITCH_EN; + writel(value, hcd->regs + XECP_PORT_CAP_REG); + } + + return 0; +} + static void cdns3_host_exit(struct cdns3 *cdns) { platform_device_unregister(cdns->host_dev); From 68ed3f3d8a057bd34254e885a6306fedc0936e50 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Fri, 22 May 2020 18:08:31 +0800 Subject: [PATCH 146/172] usb: cdns3: host: add xhci_plat_priv quirk XHCI_SKIP_PHY_INIT cdns3 manages PHY by own DRD driver, so skip the management by HCD core. Reviewed-by: Jun Li Reviewed-by: Pawel Laszczak Signed-off-by: Peter Chen --- drivers/usb/cdns3/host.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c index de8da737fa25..f84739327a16 100644 --- a/drivers/usb/cdns3/host.c +++ b/drivers/usb/cdns3/host.c @@ -24,6 +24,7 @@ #define LPM_2_STB_SWITCH_EN BIT(25) static const struct xhci_plat_priv xhci_plat_cdns3_xhci = { + .quirks = XHCI_SKIP_PHY_INIT, .suspend_quirk = xhci_cdns3_suspend_quirk, }; From 1cc6edd8a96fb3229d8309c49967713b5c79524f Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 27 Jul 2020 17:53:37 +0800 Subject: [PATCH 147/172] usb: cdns3: host: disable BEI support The Cadence xHCI doesn't support BEI well, it causes the disconnection of ISOC devices can't be detected, so we disable it. Reviewed-by: Jun Li Signed-off-by: Peter Chen --- drivers/usb/cdns3/host.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c index f84739327a16..b273ae2231d5 100644 --- a/drivers/usb/cdns3/host.c +++ b/drivers/usb/cdns3/host.c @@ -24,7 +24,7 @@ #define LPM_2_STB_SWITCH_EN BIT(25) static const struct xhci_plat_priv xhci_plat_cdns3_xhci = { - .quirks = XHCI_SKIP_PHY_INIT, + .quirks = XHCI_SKIP_PHY_INIT | XHCI_AVOID_BEI, .suspend_quirk = xhci_cdns3_suspend_quirk, }; From 7cea9657756b2c83069a775c0671ff169bce456a Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 28 Sep 2020 15:20:03 +0800 Subject: [PATCH 148/172] usb: cdns3: add quirk for enable runtime pm by default Some vendors (eg: NXP) may want to enable runtime pm by default for power saving, add one quirk for it. Reviewed-by: Jun Li Signed-off-by: Peter Chen --- drivers/usb/cdns3/core.c | 3 ++- drivers/usb/cdns3/core.h | 4 ++++ drivers/usb/cdns3/host.c | 20 +++++++++++++++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index f2dedce3a40e..54d841aa626f 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -559,7 +559,8 @@ static int cdns3_probe(struct platform_device *pdev) device_set_wakeup_capable(dev, true); pm_runtime_set_active(dev); pm_runtime_enable(dev); - pm_runtime_forbid(dev); + if (!(cdns->pdata->quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW)) + pm_runtime_forbid(dev); /* * The controller needs less time between bus and controller suspend, diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h index 8a40d53d5ede..3176f924293a 100644 --- a/drivers/usb/cdns3/core.h +++ b/drivers/usb/cdns3/core.h @@ -42,6 +42,8 @@ struct cdns3_role_driver { struct cdns3_platform_data { int (*platform_suspend)(struct device *dev, bool suspend, bool wakeup); + unsigned long quirks; +#define CDNS3_DEFAULT_PM_RUNTIME_ALLOW BIT(0) }; /** @@ -73,6 +75,7 @@ struct cdns3_platform_data { * @wakeup_pending: wakeup interrupt pending * @pdata: platform data from glue layer * @lock: spinlock structure + * @xhci_plat_data: xhci private data structure pointer */ struct cdns3 { struct device *dev; @@ -106,6 +109,7 @@ struct cdns3 { bool wakeup_pending; struct cdns3_platform_data *pdata; spinlock_t lock; + struct xhci_plat_priv *xhci_plat_data; }; int cdns3_hw_role_switch(struct cdns3 *cdns); diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c index b273ae2231d5..08103785a17a 100644 --- a/drivers/usb/cdns3/host.c +++ b/drivers/usb/cdns3/host.c @@ -52,15 +52,25 @@ static int __cdns3_host_init(struct cdns3 *cdns) goto err1; } - ret = platform_device_add_data(xhci, &xhci_plat_cdns3_xhci, + cdns->xhci_plat_data = kmemdup(&xhci_plat_cdns3_xhci, + sizeof(struct xhci_plat_priv), GFP_KERNEL); + if (!cdns->xhci_plat_data) { + ret = -ENOMEM; + goto err1; + } + + if (cdns->pdata->quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW) + cdns->xhci_plat_data->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW; + + ret = platform_device_add_data(xhci, cdns->xhci_plat_data, sizeof(struct xhci_plat_priv)); if (ret) - goto err1; + goto free_memory; ret = platform_device_add(xhci); if (ret) { dev_err(cdns->dev, "failed to register xHCI device\n"); - goto err1; + goto free_memory; } /* Glue needs to access xHCI region register for Power management */ @@ -69,6 +79,9 @@ static int __cdns3_host_init(struct cdns3 *cdns) cdns->xhci_regs = hcd->regs; return 0; + +free_memory: + kfree(cdns->xhci_plat_data); err1: platform_device_put(xhci); return ret; @@ -102,6 +115,7 @@ int xhci_cdns3_suspend_quirk(struct usb_hcd *hcd) static void cdns3_host_exit(struct cdns3 *cdns) { + kfree(cdns->xhci_plat_data); platform_device_unregister(cdns->host_dev); cdns->host_dev = NULL; cdns3_drd_host_off(cdns); From 4006239098b23d8d89633dca4f13c2485afc1e08 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 28 Sep 2020 15:34:19 +0800 Subject: [PATCH 149/172] usb: cdns3: imx: enable runtime pm by default Enable runtime pm by default Reviewed-by: Jun Li Signed-off-by: Peter Chen --- drivers/usb/cdns3/cdns3-imx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/cdns3/cdns3-imx.c b/drivers/usb/cdns3/cdns3-imx.c index 54a2d70a9c73..22a56c4dce67 100644 --- a/drivers/usb/cdns3/cdns3-imx.c +++ b/drivers/usb/cdns3/cdns3-imx.c @@ -151,6 +151,7 @@ static int cdns_imx_platform_suspend(struct device *dev, bool suspend, bool wakeup); static struct cdns3_platform_data cdns_imx_pdata = { .platform_suspend = cdns_imx_platform_suspend, + .quirks = CDNS3_DEFAULT_PM_RUNTIME_ALLOW, }; static const struct of_dev_auxdata cdns_imx_auxdata[] = { @@ -206,7 +207,6 @@ static int cdns_imx_probe(struct platform_device *pdev) device_set_wakeup_capable(dev, true); pm_runtime_set_active(dev); pm_runtime_enable(dev); - pm_runtime_forbid(dev); return ret; err: From 1bc514dfd12678e4efac5b8c5c32f91b244e0317 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Tue, 20 Oct 2020 10:44:06 +0800 Subject: [PATCH 150/172] doc: dt-binding: cdns,usb3: add wakeup-irq To support low power mode for controller, the driver needs wakeup-irq to reflect the signal changing after controller is stopped, and waking the controller up accordingly. Reviewed-by: Rob Herring Signed-off-by: Peter Chen --- Documentation/devicetree/bindings/usb/cdns,usb3.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml index d6af2794d444..a407e1143cf4 100644 --- a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml +++ b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml @@ -26,16 +26,21 @@ properties: - const: dev interrupts: + minItems: 3 items: - description: OTG/DRD controller interrupt - description: XHCI host controller interrupt - description: Device controller interrupt + - description: interrupt used to wake up core, e.g when usbcmd.rs is + cleared by xhci core, this interrupt is optional interrupt-names: + minItems: 3 items: - const: host - const: peripheral - const: otg + - const: wakeup dr_mode: enum: [host, otg, peripheral] From b4c5d446a655667fdb39c0bd2a90bcc08d26dab9 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 25 Mar 2020 15:52:29 +0800 Subject: [PATCH 151/172] usb: chipidea: add tracepoint support for udc Add basic tracepoint support for udc driver. Reviewed-by: Jun Li Signed-off-by: Peter Chen --- drivers/usb/chipidea/Makefile | 5 +- drivers/usb/chipidea/trace.c | 23 +++++++++ drivers/usb/chipidea/trace.h | 92 +++++++++++++++++++++++++++++++++++ drivers/usb/chipidea/udc.c | 10 +++- 4 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 drivers/usb/chipidea/trace.c create mode 100644 drivers/usb/chipidea/trace.h diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile index fae779a23866..6f4a3deced35 100644 --- a/drivers/usb/chipidea/Makefile +++ b/drivers/usb/chipidea/Makefile @@ -1,8 +1,11 @@ # SPDX-License-Identifier: GPL-2.0 + +# define_trace.h needs to know how to find our header +CFLAGS_trace.o := -I$(src) obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc.o ci_hdrc-y := core.o otg.o debug.o ulpi.o -ci_hdrc-$(CONFIG_USB_CHIPIDEA_UDC) += udc.o +ci_hdrc-$(CONFIG_USB_CHIPIDEA_UDC) += udc.o trace.o ci_hdrc-$(CONFIG_USB_CHIPIDEA_HOST) += host.o ci_hdrc-$(CONFIG_USB_OTG_FSM) += otg_fsm.o diff --git a/drivers/usb/chipidea/trace.c b/drivers/usb/chipidea/trace.c new file mode 100644 index 000000000000..f6402630a58e --- /dev/null +++ b/drivers/usb/chipidea/trace.c @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Chipidea Device Mode Trace Support + * + * Copyright (C) 2020 NXP + * + * Author: Peter Chen + */ + +#define CREATE_TRACE_POINTS +#include "trace.h" + +void ci_log(struct ci_hdrc *ci, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + trace_ci_log(ci, &vaf); + va_end(args); +} diff --git a/drivers/usb/chipidea/trace.h b/drivers/usb/chipidea/trace.h new file mode 100644 index 000000000000..44ffb2bb625e --- /dev/null +++ b/drivers/usb/chipidea/trace.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Trace support header file for device mode + * + * Copyright (C) 2020 NXP + * + * Author: Peter Chen + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM chipidea + +#if !defined(__LINUX_CHIPIDEA_TRACE) || defined(TRACE_HEADER_MULTI_READ) +#define __LINUX_CHIPIDEA_TRACE + +#include +#include +#include +#include "ci.h" +#include "udc.h" + +#define CHIPIDEA_MSG_MAX 500 + +void ci_log(struct ci_hdrc *ci, const char *fmt, ...); + +TRACE_EVENT(ci_log, + TP_PROTO(struct ci_hdrc *ci, struct va_format *vaf), + TP_ARGS(ci, vaf), + TP_STRUCT__entry( + __string(name, dev_name(ci->dev)) + __dynamic_array(char, msg, CHIPIDEA_MSG_MAX) + ), + TP_fast_assign( + __assign_str(name, dev_name(ci->dev)); + vsnprintf(__get_str(msg), CHIPIDEA_MSG_MAX, vaf->fmt, *vaf->va); + ), + TP_printk("%s: %s", __get_str(name), __get_str(msg)) +); + +DECLARE_EVENT_CLASS(ci_log_trb, + TP_PROTO(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq, struct td_node *td), + TP_ARGS(hwep, hwreq, td), + TP_STRUCT__entry( + __string(name, hwep->name) + __field(struct td_node *, td) + __field(struct usb_request *, req) + __field(dma_addr_t, dma) + __field(s32, td_remaining_size) + __field(u32, next) + __field(u32, token) + __field(u32, type) + ), + TP_fast_assign( + __assign_str(name, hwep->name); + __entry->req = &hwreq->req; + __entry->td = td; + __entry->dma = td->dma; + __entry->td_remaining_size = td->td_remaining_size; + __entry->next = td->ptr->next; + __entry->token = td->ptr->token; + __entry->type = usb_endpoint_type(hwep->ep.desc); + ), + TP_printk("%s: req: %p, td: %p, td_dma_address: %pad, remaining_size: %d, " + "next: %x, total bytes: %d, status: %lx", + __get_str(name), __entry->req, __entry->td, &__entry->dma, + __entry->td_remaining_size, __entry->next, + (int)((__entry->token & TD_TOTAL_BYTES) >> __ffs(TD_TOTAL_BYTES)), + __entry->token & TD_STATUS + ) +); + +DEFINE_EVENT(ci_log_trb, ci_prepare_td, + TP_PROTO(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq, struct td_node *td), + TP_ARGS(hwep, hwreq, td) +); + +DEFINE_EVENT(ci_log_trb, ci_complete_td, + TP_PROTO(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq, struct td_node *td), + TP_ARGS(hwep, hwreq, td) +); + +#endif /* __LINUX_CHIPIDEA_TRACE */ + +/* this part must be outside header guard */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +#include diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 60ea932afe2b..c16d900cdaee 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -26,6 +26,7 @@ #include "bits.h" #include "otg.h" #include "otg_fsm.h" +#include "trace.h" /* control endpoint description */ static const struct usb_endpoint_descriptor @@ -569,14 +570,18 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) if (ret) return ret; - firstnode = list_first_entry(&hwreq->tds, struct td_node, td); - lastnode = list_entry(hwreq->tds.prev, struct td_node, td); lastnode->ptr->next = cpu_to_le32(TD_TERMINATE); if (!hwreq->req.no_interrupt) lastnode->ptr->token |= cpu_to_le32(TD_IOC); + + list_for_each_entry_safe(firstnode, lastnode, &hwreq->tds, td) + trace_ci_prepare_td(hwep, hwreq, firstnode); + + firstnode = list_first_entry(&hwreq->tds, struct td_node, td); + wmb(); hwreq->req.actual = 0; @@ -671,6 +676,7 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) { tmptoken = le32_to_cpu(node->ptr->token); + trace_ci_complete_td(hwep, hwreq, node); if ((TD_STATUS_ACTIVE & tmptoken) != 0) { int n = hw_ep_bit(hwep->num, hwep->dir); From 429ad75f2b13ac8a2c7af859f8f61b9188bca7ba Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Fri, 13 Nov 2020 15:39:22 +0800 Subject: [PATCH 152/172] usb: chipidea: trace: fix the endian issue "sparse warnings: (new ones prefixed by >>)" drivers/usb/chipidea/trace.c: note: in included file (through include/trace/trace_events.h, include/trace/define_trace.h, drivers/usb/chipidea/trace.h): >> drivers/usb/chipidea/./trace.h:39:1: sparse: sparse: incorrect type in assignment (different base types) @@ expected unsigned int [usertype] next @@ got restricted __le32 [usertype] next @@ >> drivers/usb/chipidea/./trace.h:39:1: sparse: expected unsigned int [usertype] next >> drivers/usb/chipidea/./trace.h:39:1: sparse: got restricted __le32 [usertype] next >> drivers/usb/chipidea/./trace.h:39:1: sparse: sparse: incorrect type in assignment (different base types) @@ expected unsigned int [usertype] token @@ got restricted __le32 [usertype] token @@ >> drivers/usb/chipidea/./trace.h:39:1: sparse: expected unsigned int [usertype] token >> drivers/usb/chipidea/./trace.h:39:1: sparse: got restricted __le32 [usertype] token drivers/usb/chipidea/trace.c: note: in included file (through include/trace/perf.h, include/trace/define_trace.h, drivers/usb/chipidea/trace.h): >> drivers/usb/chipidea/./trace.h:39:1: sparse: sparse: incorrect type in assignment (different base types) @@ expected unsigned int [usertype] next @@ got restricted __le32 [usertype] next @@ >> drivers/usb/chipidea/./trace.h:39:1: sparse: expected unsigned int [usertype] next >> drivers/usb/chipidea/./trace.h:39:1: sparse: got restricted __le32 [usertype] next >> drivers/usb/chipidea/./trace.h:39:1: sparse: sparse: incorrect type in assignment (different base types) @@ expected unsigned int [usertype] token @@ got restricted __le32 [usertype] token @@ >> drivers/usb/chipidea/./trace.h:39:1: sparse: expected unsigned int [usertype] token >> drivers/usb/chipidea/./trace.h:39:1: sparse: got restricted __le32 [usertype] token Reported-by: kernel test robot Signed-off-by: Peter Chen --- drivers/usb/chipidea/trace.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/chipidea/trace.h b/drivers/usb/chipidea/trace.h index 44ffb2bb625e..1601fd86c4c1 100644 --- a/drivers/usb/chipidea/trace.h +++ b/drivers/usb/chipidea/trace.h @@ -56,8 +56,8 @@ DECLARE_EVENT_CLASS(ci_log_trb, __entry->td = td; __entry->dma = td->dma; __entry->td_remaining_size = td->td_remaining_size; - __entry->next = td->ptr->next; - __entry->token = td->ptr->token; + __entry->next = le32_to_cpu(td->ptr->next); + __entry->token = le32_to_cpu(td->ptr->token); __entry->type = usb_endpoint_type(hwep->ep.desc); ), TP_printk("%s: req: %p, td: %p, td_dma_address: %pad, remaining_size: %d, " From 448373d9db1a7000072f65103af19e20503f0c0c Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Mon, 23 Nov 2020 12:49:31 +0200 Subject: [PATCH 153/172] usb: cdns3: fix NULL pointer dereference on no platform data Some platforms (e.g. TI) will not have any platform data which will lead to NULL pointer dereference if we don't check for NULL pdata. Fixes: 7cea9657756b ("usb: cdns3: add quirk for enable runtime pm by default") Reported-by: Nishanth Menon Signed-off-by: Roger Quadros Acked-by: Pawel Laszczak Signed-off-by: Peter Chen --- drivers/usb/cdns3/core.c | 2 +- drivers/usb/cdns3/host.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index 54d841aa626f..0f08aebce86d 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -559,7 +559,7 @@ static int cdns3_probe(struct platform_device *pdev) device_set_wakeup_capable(dev, true); pm_runtime_set_active(dev); pm_runtime_enable(dev); - if (!(cdns->pdata->quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW)) + if (!(cdns->pdata && (cdns->pdata->quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW))) pm_runtime_forbid(dev); /* diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c index 08103785a17a..ec89f2e5430f 100644 --- a/drivers/usb/cdns3/host.c +++ b/drivers/usb/cdns3/host.c @@ -59,7 +59,7 @@ static int __cdns3_host_init(struct cdns3 *cdns) goto err1; } - if (cdns->pdata->quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW) + if (cdns->pdata && (cdns->pdata->quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW)) cdns->xhci_plat_data->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW; ret = platform_device_add_data(xhci, cdns->xhci_plat_data, From 58bcafec8062f7566b736a019c3506949f9be6d3 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Tue, 24 Nov 2020 13:39:11 -0300 Subject: [PATCH 154/172] usb: chipidea: usbmisc_imx: Use of_device_get_match_data() The retrieval of driver data via of_device_get_match_data() can make the code simpler. Use of_device_get_match_data() to simplify the code. Signed-off-by: Fabio Estevam Signed-off-by: Peter Chen --- drivers/usb/chipidea/usbmisc_imx.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c index 6d8331e7da99..4545b23bda3f 100644 --- a/drivers/usb/chipidea/usbmisc_imx.c +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -1134,11 +1134,6 @@ MODULE_DEVICE_TABLE(of, usbmisc_imx_dt_ids); static int usbmisc_imx_probe(struct platform_device *pdev) { struct imx_usbmisc *data; - const struct of_device_id *of_id; - - of_id = of_match_device(usbmisc_imx_dt_ids, &pdev->dev); - if (!of_id) - return -ENODEV; data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) @@ -1150,7 +1145,7 @@ static int usbmisc_imx_probe(struct platform_device *pdev) if (IS_ERR(data->base)) return PTR_ERR(data->base); - data->ops = (const struct usbmisc_ops *)of_id->data; + data->ops = of_device_get_match_data(&pdev->dev); platform_set_drvdata(pdev, data); return 0; From 59b7c6a8fd6c44d2683d02d63e137d0c11855c32 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Tue, 24 Nov 2020 13:39:12 -0300 Subject: [PATCH 155/172] usb: chipidea: ci_hdrc_imx: Use of_device_get_match_data() The retrieval of driver data via of_device_get_match_data() can make the code simpler. Use of_device_get_match_data() to simplify the code. Signed-off-by: Fabio Estevam Signed-off-by: Peter Chen --- drivers/usb/chipidea/ci_hdrc_imx.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 25c65accf089..8fa712148e5d 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -319,16 +319,11 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) .notify_event = ci_hdrc_imx_notify_event, }; int ret; - const struct of_device_id *of_id; const struct ci_hdrc_imx_platform_flag *imx_platform_flag; struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; - of_id = of_match_device(ci_hdrc_imx_dt_ids, dev); - if (!of_id) - return -ENODEV; - - imx_platform_flag = of_id->data; + imx_platform_flag = of_device_get_match_data(&pdev->dev); data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) From 8435ff0fd7e15ab4ec9e53107c9afefa23717e0f Mon Sep 17 00:00:00 2001 From: Aswath Govindraju Date: Tue, 8 Dec 2020 10:38:06 +0530 Subject: [PATCH 156/172] MAINTAINERS: Add myself as a reviewer for CADENCE USB3 DRD IP DRIVER I would like to help in reviewing CADENCE USB3 DRD IP DRIVER patches Signed-off-by: Aswath Govindraju Signed-off-by: Peter Chen --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 2daa6ee673f7..277bc9c6683f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3846,6 +3846,7 @@ CADENCE USB3 DRD IP DRIVER M: Peter Chen M: Pawel Laszczak M: Roger Quadros +R: Aswath Govindraju L: linux-usb@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/usb.git From beff5de94fd45dc68f65b45634013184b6d66519 Mon Sep 17 00:00:00 2001 From: Utkarsh Patel Date: Tue, 8 Dec 2020 20:24:08 -0800 Subject: [PATCH 157/172] usb: typec: intel_pmc_mux: Configure cable generation value for USB4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit USB4 specification chapter 3 indicates that cable data rates have to be rounded for USB4 device to operate as USB4. With that configure cable generation value to use rounded data rates for USB4. Reviewed-by: Heikki Krogerus  Signed-off-by: Utkarsh Patel Link: https://lore.kernel.org/r/20201209042408.23079-2-utkarsh.h.patel@intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux/intel_pmc_mux.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c index e58ae8a7fefb..cf37a59ce130 100644 --- a/drivers/usb/typec/mux/intel_pmc_mux.c +++ b/drivers/usb/typec/mux/intel_pmc_mux.c @@ -327,6 +327,11 @@ pmc_usb_mux_usb4(struct pmc_usb_port *port, struct typec_mux_state *state) fallthrough; default: req.mode_data |= PMC_USB_ALTMODE_ACTIVE_CABLE; + + /* Configure data rate to rounded in the case of Active TBT3 + * and USB4 cables. + */ + req.mode_data |= PMC_USB_ALTMODE_TBT_GEN(1); break; } From b00f444f9add39b64d1943fa75538a1ebd54a290 Mon Sep 17 00:00:00 2001 From: Will McVicker Date: Fri, 27 Nov 2020 15:05:55 +0100 Subject: [PATCH 158/172] USB: gadget: f_rndis: fix bitrate for SuperSpeed and above Align the SuperSpeed Plus bitrate for f_rndis to match f_ncm's ncm_bitrate defined by commit 1650113888fe ("usb: gadget: f_ncm: add SuperSpeed descriptors for CDC NCM"). Cc: Felipe Balbi Cc: EJ Hsu Cc: Peter Chen Cc: stable Signed-off-by: Will McVicker Reviewed-by: Peter Chen Link: https://lore.kernel.org/r/20201127140559.381351-2-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_rndis.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/function/f_rndis.c b/drivers/usb/gadget/function/f_rndis.c index 9534c8ab62a8..0739b05a0ef7 100644 --- a/drivers/usb/gadget/function/f_rndis.c +++ b/drivers/usb/gadget/function/f_rndis.c @@ -87,8 +87,10 @@ static inline struct f_rndis *func_to_rndis(struct usb_function *f) /* peak (theoretical) bulk transfer rate in bits-per-second */ static unsigned int bitrate(struct usb_gadget *g) { + if (gadget_is_superspeed(g) && g->speed >= USB_SPEED_SUPER_PLUS) + return 4250000000U; if (gadget_is_superspeed(g) && g->speed == USB_SPEED_SUPER) - return 13 * 1024 * 8 * 1000 * 8; + return 3750000000U; else if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) return 13 * 512 * 8 * 1000 * 8; else From 3ee05c20656782387aa9eb010fdb9bb16982ac3f Mon Sep 17 00:00:00 2001 From: "taehyun.cho" Date: Fri, 27 Nov 2020 15:05:56 +0100 Subject: [PATCH 159/172] USB: gadget: f_acm: add support for SuperSpeed Plus Setup the SuperSpeed Plus descriptors for f_acm. This allows the gadget to work properly without crashing at SuperSpeed rates. Cc: Felipe Balbi Cc: stable Signed-off-by: taehyun.cho Signed-off-by: Will McVicker Reviewed-by: Peter Chen Link: https://lore.kernel.org/r/20201127140559.381351-3-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_acm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/function/f_acm.c b/drivers/usb/gadget/function/f_acm.c index 46647bfac2ef..349945e064bb 100644 --- a/drivers/usb/gadget/function/f_acm.c +++ b/drivers/usb/gadget/function/f_acm.c @@ -686,7 +686,7 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) acm_ss_out_desc.bEndpointAddress = acm_fs_out_desc.bEndpointAddress; status = usb_assign_descriptors(f, acm_fs_function, acm_hs_function, - acm_ss_function, NULL); + acm_ss_function, acm_ss_function); if (status) goto fail; From 457a902ba1a73b7720666b21ca038cd19764db18 Mon Sep 17 00:00:00 2001 From: Will McVicker Date: Fri, 27 Nov 2020 15:05:57 +0100 Subject: [PATCH 160/172] USB: gadget: f_midi: setup SuperSpeed Plus descriptors Needed for SuperSpeed Plus support for f_midi. This allows the gadget to work properly without crashing at SuperSpeed rates. Cc: Felipe Balbi Cc: stable Signed-off-by: Will McVicker Reviewed-by: Peter Chen Link: https://lore.kernel.org/r/20201127140559.381351-4-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_midi.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c index 19d97940eeb9..8fff995b8dd5 100644 --- a/drivers/usb/gadget/function/f_midi.c +++ b/drivers/usb/gadget/function/f_midi.c @@ -1048,6 +1048,12 @@ static int f_midi_bind(struct usb_configuration *c, struct usb_function *f) f->ss_descriptors = usb_copy_descriptors(midi_function); if (!f->ss_descriptors) goto fail_f_midi; + + if (gadget_is_superspeed_plus(c->cdev->gadget)) { + f->ssp_descriptors = usb_copy_descriptors(midi_function); + if (!f->ssp_descriptors) + goto fail_f_midi; + } } kfree(midi_function); From a353397b0d5dfa3c99b372505db3378fc919c6c6 Mon Sep 17 00:00:00 2001 From: Jack Pham Date: Tue, 27 Oct 2020 16:07:31 -0700 Subject: [PATCH 161/172] usb: gadget: f_fs: Re-use SS descriptors for SuperSpeedPlus In many cases a function that supports SuperSpeed can very well operate in SuperSpeedPlus, if a gadget controller supports it, as the endpoint descriptors (and companion descriptors) are generally identical and can be re-used. This is true for two commonly used functions: Android's ADB and MTP. So we can simply assign the usb_function's ssp_descriptors array to point to its ss_descriptors, if available. Similarly, we need to allow an epfile's ioctl for FUNCTIONFS_ENDPOINT_DESC to correctly return the corresponding SuperSpeed endpoint descriptor in case the connected speed is SuperSpeedPlus as well. The only exception is if a function wants to implement an Isochronous endpoint capable of transferring more than 48KB per service interval when operating at greater than USB 3.1 Gen1 speed, in which case it would require an additional SuperSpeedPlus Isochronous Endpoint Companion descriptor to be returned as part of the Configuration Descriptor. Support for that would need to be separately added to the userspace-facing FunctionFS API which may not be a trivial task--likely a new descriptor format (v3?) may need to be devised to allow for separate SS and SSP descriptors to be supplied. Signed-off-by: Jack Pham Cc: stable Link: https://lore.kernel.org/r/20201027230731.9073-1-jackp@codeaurora.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_fs.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 8f5ceacdc5f1..78c003fb05fd 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -1330,6 +1330,7 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, switch (epfile->ffs->gadget->speed) { case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: desc_idx = 2; break; case USB_SPEED_HIGH: @@ -3176,7 +3177,8 @@ static int _ffs_func_bind(struct usb_configuration *c, } if (likely(super)) { - func->function.ss_descriptors = vla_ptr(vlabuf, d, ss_descs); + func->function.ss_descriptors = func->function.ssp_descriptors = + vla_ptr(vlabuf, d, ss_descs); ss_len = ffs_do_descs(ffs->ss_descs_count, vla_ptr(vlabuf, d, raw_descs) + fs_len + hs_len, d_raw_descs__sz - fs_len - hs_len, @@ -3586,6 +3588,7 @@ static void ffs_func_unbind(struct usb_configuration *c, func->function.fs_descriptors = NULL; func->function.hs_descriptors = NULL; func->function.ss_descriptors = NULL; + func->function.ssp_descriptors = NULL; func->interfaces_nums = NULL; ffs_event_add(ffs, FUNCTIONFS_UNBIND); From 8704fd73bf5658bf4b827643f7f526481082d83f Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 27 Nov 2020 15:05:59 +0100 Subject: [PATCH 162/172] USB: gadget: f_fs: remove likely/unlikely They are used way too often in this file, in some ways that are actually wrong. Almost all of these are already known by the compiler and CPU so just remove them all as none of these should be on any "hot paths" where it actually matters. Cc: Felipe Balbi Reported-by: Peter Chen Reviewed-by: Peter Chen Link: https://lore.kernel.org/r/20201127140559.381351-6-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_fs.c | 177 ++++++++++++++--------------- 1 file changed, 88 insertions(+), 89 deletions(-) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 78c003fb05fd..801a8b668a35 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -296,11 +296,11 @@ static int __ffs_ep0_queue_wait(struct ffs_data *ffs, char *data, size_t len) reinit_completion(&ffs->ep0req_completion); ret = usb_ep_queue(ffs->gadget->ep0, req, GFP_ATOMIC); - if (unlikely(ret < 0)) + if (ret < 0) return ret; ret = wait_for_completion_interruptible(&ffs->ep0req_completion); - if (unlikely(ret)) { + if (ret) { usb_ep_dequeue(ffs->gadget->ep0, req); return -EINTR; } @@ -337,7 +337,7 @@ static ssize_t ffs_ep0_write(struct file *file, const char __user *buf, /* Acquire mutex */ ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); - if (unlikely(ret < 0)) + if (ret < 0) return ret; /* Check state */ @@ -345,7 +345,7 @@ static ssize_t ffs_ep0_write(struct file *file, const char __user *buf, case FFS_READ_DESCRIPTORS: case FFS_READ_STRINGS: /* Copy data */ - if (unlikely(len < 16)) { + if (len < 16) { ret = -EINVAL; break; } @@ -360,7 +360,7 @@ static ssize_t ffs_ep0_write(struct file *file, const char __user *buf, if (ffs->state == FFS_READ_DESCRIPTORS) { pr_info("read descriptors\n"); ret = __ffs_data_got_descs(ffs, data, len); - if (unlikely(ret < 0)) + if (ret < 0) break; ffs->state = FFS_READ_STRINGS; @@ -368,11 +368,11 @@ static ssize_t ffs_ep0_write(struct file *file, const char __user *buf, } else { pr_info("read strings\n"); ret = __ffs_data_got_strings(ffs, data, len); - if (unlikely(ret < 0)) + if (ret < 0) break; ret = ffs_epfiles_create(ffs); - if (unlikely(ret)) { + if (ret) { ffs->state = FFS_CLOSING; break; } @@ -381,7 +381,7 @@ static ssize_t ffs_ep0_write(struct file *file, const char __user *buf, mutex_unlock(&ffs->mutex); ret = ffs_ready(ffs); - if (unlikely(ret < 0)) { + if (ret < 0) { ffs->state = FFS_CLOSING; return ret; } @@ -495,7 +495,7 @@ static ssize_t __ffs_ep0_read_events(struct ffs_data *ffs, char __user *buf, spin_unlock_irq(&ffs->ev.waitq.lock); mutex_unlock(&ffs->mutex); - return unlikely(copy_to_user(buf, events, size)) ? -EFAULT : size; + return copy_to_user(buf, events, size) ? -EFAULT : size; } static ssize_t ffs_ep0_read(struct file *file, char __user *buf, @@ -514,7 +514,7 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, /* Acquire mutex */ ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); - if (unlikely(ret < 0)) + if (ret < 0) return ret; /* Check state */ @@ -536,7 +536,7 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, case FFS_NO_SETUP: n = len / sizeof(struct usb_functionfs_event); - if (unlikely(!n)) { + if (!n) { ret = -EINVAL; break; } @@ -567,9 +567,9 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, spin_unlock_irq(&ffs->ev.waitq.lock); - if (likely(len)) { + if (len) { data = kmalloc(len, GFP_KERNEL); - if (unlikely(!data)) { + if (!data) { ret = -ENOMEM; goto done_mutex; } @@ -586,7 +586,7 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf, /* unlocks spinlock */ ret = __ffs_ep0_queue_wait(ffs, data, len); - if (likely(ret > 0) && unlikely(copy_to_user(buf, data, len))) + if ((ret > 0) && (copy_to_user(buf, data, len))) ret = -EFAULT; goto done_mutex; @@ -608,7 +608,7 @@ static int ffs_ep0_open(struct inode *inode, struct file *file) ENTER(); - if (unlikely(ffs->state == FFS_CLOSING)) + if (ffs->state == FFS_CLOSING) return -EBUSY; file->private_data = ffs; @@ -657,7 +657,7 @@ static __poll_t ffs_ep0_poll(struct file *file, poll_table *wait) poll_wait(file, &ffs->ev.waitq, wait); ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK); - if (unlikely(ret < 0)) + if (ret < 0) return mask; switch (ffs->state) { @@ -708,7 +708,7 @@ static const struct file_operations ffs_ep0_operations = { static void ffs_epfile_io_complete(struct usb_ep *_ep, struct usb_request *req) { ENTER(); - if (likely(req->context)) { + if (req->context) { struct ffs_ep *ep = _ep->driver_data; ep->status = req->status ? req->status : req->actual; complete(req->context); @@ -718,10 +718,10 @@ static void ffs_epfile_io_complete(struct usb_ep *_ep, struct usb_request *req) static ssize_t ffs_copy_to_iter(void *data, int data_len, struct iov_iter *iter) { ssize_t ret = copy_to_iter(data, data_len, iter); - if (likely(ret == data_len)) + if (ret == data_len) return ret; - if (unlikely(iov_iter_count(iter))) + if (iov_iter_count(iter)) return -EFAULT; /* @@ -887,7 +887,7 @@ static ssize_t __ffs_epfile_read_buffered(struct ffs_epfile *epfile, return ret; } - if (unlikely(iov_iter_count(iter))) { + if (iov_iter_count(iter)) { ret = -EFAULT; } else { buf->length -= ret; @@ -908,10 +908,10 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile, struct ffs_buffer *buf; ssize_t ret = copy_to_iter(data, data_len, iter); - if (likely(data_len == ret)) + if (data_len == ret) return ret; - if (unlikely(iov_iter_count(iter))) + if (iov_iter_count(iter)) return -EFAULT; /* See ffs_copy_to_iter for more context. */ @@ -932,7 +932,7 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile, * in struct ffs_epfile for full read_buffer pointer synchronisation * story. */ - if (unlikely(cmpxchg(&epfile->read_buffer, NULL, buf))) + if (cmpxchg(&epfile->read_buffer, NULL, buf)) kfree(buf); return ret; @@ -970,7 +970,7 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) /* We will be using request and read_buffer */ ret = ffs_mutex_lock(&epfile->mutex, file->f_flags & O_NONBLOCK); - if (unlikely(ret)) + if (ret) goto error; /* Allocate & copy */ @@ -1015,7 +1015,7 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) spin_unlock_irq(&epfile->ffs->eps_lock); data = ffs_alloc_buffer(io_data, data_len); - if (unlikely(!data)) { + if (!data) { ret = -ENOMEM; goto error_mutex; } @@ -1035,7 +1035,7 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) ret = usb_ep_set_halt(ep->ep); if (!ret) ret = -EBADMSG; - } else if (unlikely(data_len == -EINVAL)) { + } else if (data_len == -EINVAL) { /* * Sanity Check: even though data_len can't be used * uninitialized at the time I write this comment, some @@ -1070,12 +1070,12 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) req->complete = ffs_epfile_io_complete; ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC); - if (unlikely(ret < 0)) + if (ret < 0) goto error_lock; spin_unlock_irq(&epfile->ffs->eps_lock); - if (unlikely(wait_for_completion_interruptible(&done))) { + if (wait_for_completion_interruptible(&done)) { /* * To avoid race condition with ffs_epfile_io_complete, * dequeue the request first then check @@ -1117,7 +1117,7 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) req->complete = ffs_epfile_async_io_complete; ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC); - if (unlikely(ret)) { + if (ret) { io_data->req = NULL; usb_ep_free_request(ep->ep, req); goto error_lock; @@ -1168,7 +1168,7 @@ static int ffs_aio_cancel(struct kiocb *kiocb) spin_lock_irqsave(&epfile->ffs->eps_lock, flags); - if (likely(io_data && io_data->ep && io_data->req)) + if (io_data && io_data->ep && io_data->req) value = usb_ep_dequeue(io_data->ep, io_data->req); else value = -EINVAL; @@ -1187,7 +1187,7 @@ static ssize_t ffs_epfile_write_iter(struct kiocb *kiocb, struct iov_iter *from) if (!is_sync_kiocb(kiocb)) { p = kzalloc(sizeof(io_data), GFP_KERNEL); - if (unlikely(!p)) + if (!p) return -ENOMEM; p->aio = true; } else { @@ -1224,7 +1224,7 @@ static ssize_t ffs_epfile_read_iter(struct kiocb *kiocb, struct iov_iter *to) if (!is_sync_kiocb(kiocb)) { p = kzalloc(sizeof(io_data), GFP_KERNEL); - if (unlikely(!p)) + if (!p) return -ENOMEM; p->aio = true; } else { @@ -1388,7 +1388,7 @@ ffs_sb_make_inode(struct super_block *sb, void *data, inode = new_inode(sb); - if (likely(inode)) { + if (inode) { struct timespec64 ts = current_time(inode); inode->i_ino = get_next_ino(); @@ -1420,11 +1420,11 @@ static struct dentry *ffs_sb_create_file(struct super_block *sb, ENTER(); dentry = d_alloc_name(sb->s_root, name); - if (unlikely(!dentry)) + if (!dentry) return NULL; inode = ffs_sb_make_inode(sb, data, fops, NULL, &ffs->file_perms); - if (unlikely(!inode)) { + if (!inode) { dput(dentry); return NULL; } @@ -1471,12 +1471,11 @@ static int ffs_sb_fill(struct super_block *sb, struct fs_context *fc) &simple_dir_inode_operations, &data->perms); sb->s_root = d_make_root(inode); - if (unlikely(!sb->s_root)) + if (!sb->s_root) return -ENOMEM; /* EP0 file */ - if (unlikely(!ffs_sb_create_file(sb, "ep0", ffs, - &ffs_ep0_operations))) + if (!ffs_sb_create_file(sb, "ep0", ffs, &ffs_ep0_operations)) return -ENOMEM; return 0; @@ -1564,13 +1563,13 @@ static int ffs_fs_get_tree(struct fs_context *fc) return invalf(fc, "No source specified"); ffs = ffs_data_new(fc->source); - if (unlikely(!ffs)) + if (!ffs) return -ENOMEM; ffs->file_perms = ctx->perms; ffs->no_disconnect = ctx->no_disconnect; ffs->dev_name = kstrdup(fc->source, GFP_KERNEL); - if (unlikely(!ffs->dev_name)) { + if (!ffs->dev_name) { ffs_data_put(ffs); return -ENOMEM; } @@ -1656,7 +1655,7 @@ static int functionfs_init(void) ENTER(); ret = register_filesystem(&ffs_fs_type); - if (likely(!ret)) + if (!ret) pr_info("file system registered\n"); else pr_err("failed registering file system (%d)\n", ret); @@ -1701,7 +1700,7 @@ static void ffs_data_put(struct ffs_data *ffs) { ENTER(); - if (unlikely(refcount_dec_and_test(&ffs->ref))) { + if (refcount_dec_and_test(&ffs->ref)) { pr_info("%s(): freeing\n", __func__); ffs_data_clear(ffs); BUG_ON(waitqueue_active(&ffs->ev.waitq) || @@ -1743,7 +1742,7 @@ static void ffs_data_closed(struct ffs_data *ffs) static struct ffs_data *ffs_data_new(const char *dev_name) { struct ffs_data *ffs = kzalloc(sizeof *ffs, GFP_KERNEL); - if (unlikely(!ffs)) + if (!ffs) return NULL; ENTER(); @@ -1833,11 +1832,11 @@ static int functionfs_bind(struct ffs_data *ffs, struct usb_composite_dev *cdev) return -EBADFD; first_id = usb_string_ids_n(cdev, ffs->strings_count); - if (unlikely(first_id < 0)) + if (first_id < 0) return first_id; ffs->ep0req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL); - if (unlikely(!ffs->ep0req)) + if (!ffs->ep0req) return -ENOMEM; ffs->ep0req->complete = ffs_ep0_complete; ffs->ep0req->context = ffs; @@ -1893,7 +1892,7 @@ static int ffs_epfiles_create(struct ffs_data *ffs) epfile->dentry = ffs_sb_create_file(ffs->sb, epfile->name, epfile, &ffs_epfile_operations); - if (unlikely(!epfile->dentry)) { + if (!epfile->dentry) { ffs_epfiles_destroy(epfiles, i - 1); return -ENOMEM; } @@ -1931,7 +1930,7 @@ static void ffs_func_eps_disable(struct ffs_function *func) spin_lock_irqsave(&func->ffs->eps_lock, flags); while (count--) { /* pending requests get nuked */ - if (likely(ep->ep)) + if (ep->ep) usb_ep_disable(ep->ep); ++ep; @@ -1965,7 +1964,7 @@ static int ffs_func_eps_enable(struct ffs_function *func) } ret = usb_ep_enable(ep->ep); - if (likely(!ret)) { + if (!ret) { epfile->ep = ep; epfile->in = usb_endpoint_dir_in(ep->ep->desc); epfile->isoc = usb_endpoint_xfer_isoc(ep->ep->desc); @@ -2038,12 +2037,12 @@ static int __must_check ffs_do_single_desc(char *data, unsigned len, #define __entity_check_ENDPOINT(val) ((val) & USB_ENDPOINT_NUMBER_MASK) #define __entity(type, val) do { \ pr_vdebug("entity " #type "(%02x)\n", (val)); \ - if (unlikely(!__entity_check_ ##type(val))) { \ + if (!__entity_check_ ##type(val)) { \ pr_vdebug("invalid entity's value\n"); \ return -EINVAL; \ } \ ret = entity(FFS_ ##type, &val, _ds, priv); \ - if (unlikely(ret < 0)) { \ + if (ret < 0) { \ pr_debug("entity " #type "(%02x); ret = %d\n", \ (val), ret); \ return ret; \ @@ -2168,7 +2167,7 @@ static int __must_check ffs_do_descs(unsigned count, char *data, unsigned len, /* Record "descriptor" entity */ ret = entity(FFS_DESCRIPTOR, (u8 *)num, (void *)data, priv); - if (unlikely(ret < 0)) { + if (ret < 0) { pr_debug("entity DESCRIPTOR(%02lx); ret = %d\n", num, ret); return ret; @@ -2179,7 +2178,7 @@ static int __must_check ffs_do_descs(unsigned count, char *data, unsigned len, ret = ffs_do_single_desc(data, len, entity, priv, ¤t_class); - if (unlikely(ret < 0)) { + if (ret < 0) { pr_debug("%s returns %d\n", __func__, ret); return ret; } @@ -2285,7 +2284,7 @@ static int __must_check ffs_do_single_os_desc(char *data, unsigned len, /* loop over all ext compat/ext prop descriptors */ while (feature_count--) { ret = entity(type, h, data, len, priv); - if (unlikely(ret < 0)) { + if (ret < 0) { pr_debug("bad OS descriptor, type: %d\n", type); return ret; } @@ -2325,7 +2324,7 @@ static int __must_check ffs_do_os_descs(unsigned count, return -EINVAL; ret = __ffs_do_os_desc_header(&type, desc); - if (unlikely(ret < 0)) { + if (ret < 0) { pr_debug("entity OS_DESCRIPTOR(%02lx); ret = %d\n", num, ret); return ret; @@ -2346,7 +2345,7 @@ static int __must_check ffs_do_os_descs(unsigned count, */ ret = ffs_do_single_os_desc(data, len, type, feature_count, entity, priv, desc); - if (unlikely(ret < 0)) { + if (ret < 0) { pr_debug("%s returns %d\n", __func__, ret); return ret; } @@ -2578,20 +2577,20 @@ static int __ffs_data_got_strings(struct ffs_data *ffs, ENTER(); - if (unlikely(len < 16 || - get_unaligned_le32(data) != FUNCTIONFS_STRINGS_MAGIC || - get_unaligned_le32(data + 4) != len)) + if (len < 16 || + get_unaligned_le32(data) != FUNCTIONFS_STRINGS_MAGIC || + get_unaligned_le32(data + 4) != len) goto error; str_count = get_unaligned_le32(data + 8); lang_count = get_unaligned_le32(data + 12); /* if one is zero the other must be zero */ - if (unlikely(!str_count != !lang_count)) + if (!str_count != !lang_count) goto error; /* Do we have at least as many strings as descriptors need? */ needed_count = ffs->strings_count; - if (unlikely(str_count < needed_count)) + if (str_count < needed_count) goto error; /* @@ -2615,7 +2614,7 @@ static int __ffs_data_got_strings(struct ffs_data *ffs, char *vlabuf = kmalloc(vla_group_size(d), GFP_KERNEL); - if (unlikely(!vlabuf)) { + if (!vlabuf) { kfree(_data); return -ENOMEM; } @@ -2642,7 +2641,7 @@ static int __ffs_data_got_strings(struct ffs_data *ffs, do { /* lang_count > 0 so we can use do-while */ unsigned needed = needed_count; - if (unlikely(len < 3)) + if (len < 3) goto error_free; t->language = get_unaligned_le16(data); t->strings = s; @@ -2655,7 +2654,7 @@ static int __ffs_data_got_strings(struct ffs_data *ffs, do { /* str_count > 0 so we can use do-while */ size_t length = strnlen(data, len); - if (unlikely(length == len)) + if (length == len) goto error_free; /* @@ -2663,7 +2662,7 @@ static int __ffs_data_got_strings(struct ffs_data *ffs, * if that's the case we simply ignore the * rest */ - if (likely(needed)) { + if (needed) { /* * s->id will be set while adding * function to configuration so for @@ -2685,7 +2684,7 @@ static int __ffs_data_got_strings(struct ffs_data *ffs, } while (--lang_count); /* Some garbage left? */ - if (unlikely(len)) + if (len) goto error_free; /* Done! */ @@ -2832,7 +2831,7 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep, ffs_ep = func->eps + idx; - if (unlikely(ffs_ep->descs[ep_desc_id])) { + if (ffs_ep->descs[ep_desc_id]) { pr_err("two %sspeed descriptors for EP %d\n", speed_names[ep_desc_id], ds->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); @@ -2863,12 +2862,12 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep, wMaxPacketSize = ds->wMaxPacketSize; pr_vdebug("autoconfig\n"); ep = usb_ep_autoconfig(func->gadget, ds); - if (unlikely(!ep)) + if (!ep) return -ENOTSUPP; ep->driver_data = func->eps + idx; req = usb_ep_alloc_request(ep, GFP_KERNEL); - if (unlikely(!req)) + if (!req) return -ENOMEM; ffs_ep->ep = ep; @@ -2910,7 +2909,7 @@ static int __ffs_func_bind_do_nums(enum ffs_entity_type type, u8 *valuep, idx = *valuep; if (func->interfaces_nums[idx] < 0) { int id = usb_interface_id(func->conf, &func->function); - if (unlikely(id < 0)) + if (id < 0) return id; func->interfaces_nums[idx] = id; } @@ -2931,7 +2930,7 @@ static int __ffs_func_bind_do_nums(enum ffs_entity_type type, u8 *valuep, return 0; idx = (*valuep & USB_ENDPOINT_NUMBER_MASK) - 1; - if (unlikely(!func->eps[idx].ep)) + if (!func->eps[idx].ep) return -EINVAL; { @@ -3114,12 +3113,12 @@ static int _ffs_func_bind(struct usb_configuration *c, ENTER(); /* Has descriptors only for speeds gadget does not support */ - if (unlikely(!(full | high | super))) + if (!(full | high | super)) return -ENOTSUPP; /* Allocate a single chunk, less management later on */ vlabuf = kzalloc(vla_group_size(d), GFP_KERNEL); - if (unlikely(!vlabuf)) + if (!vlabuf) return -ENOMEM; ffs->ms_os_descs_ext_prop_avail = vla_ptr(vlabuf, d, ext_prop); @@ -3148,13 +3147,13 @@ static int _ffs_func_bind(struct usb_configuration *c, * endpoints first, so that later we can rewrite the endpoint * numbers without worrying that it may be described later on. */ - if (likely(full)) { + if (full) { func->function.fs_descriptors = vla_ptr(vlabuf, d, fs_descs); fs_len = ffs_do_descs(ffs->fs_descs_count, vla_ptr(vlabuf, d, raw_descs), d_raw_descs__sz, __ffs_func_bind_do_descs, func); - if (unlikely(fs_len < 0)) { + if (fs_len < 0) { ret = fs_len; goto error; } @@ -3162,13 +3161,13 @@ static int _ffs_func_bind(struct usb_configuration *c, fs_len = 0; } - if (likely(high)) { + if (high) { func->function.hs_descriptors = vla_ptr(vlabuf, d, hs_descs); hs_len = ffs_do_descs(ffs->hs_descs_count, vla_ptr(vlabuf, d, raw_descs) + fs_len, d_raw_descs__sz - fs_len, __ffs_func_bind_do_descs, func); - if (unlikely(hs_len < 0)) { + if (hs_len < 0) { ret = hs_len; goto error; } @@ -3176,14 +3175,14 @@ static int _ffs_func_bind(struct usb_configuration *c, hs_len = 0; } - if (likely(super)) { + if (super) { func->function.ss_descriptors = func->function.ssp_descriptors = vla_ptr(vlabuf, d, ss_descs); ss_len = ffs_do_descs(ffs->ss_descs_count, vla_ptr(vlabuf, d, raw_descs) + fs_len + hs_len, d_raw_descs__sz - fs_len - hs_len, __ffs_func_bind_do_descs, func); - if (unlikely(ss_len < 0)) { + if (ss_len < 0) { ret = ss_len; goto error; } @@ -3201,7 +3200,7 @@ static int _ffs_func_bind(struct usb_configuration *c, (super ? ffs->ss_descs_count : 0), vla_ptr(vlabuf, d, raw_descs), d_raw_descs__sz, __ffs_func_bind_do_nums, func); - if (unlikely(ret < 0)) + if (ret < 0) goto error; func->function.os_desc_table = vla_ptr(vlabuf, d, os_desc_table); @@ -3222,7 +3221,7 @@ static int _ffs_func_bind(struct usb_configuration *c, d_raw_descs__sz - fs_len - hs_len - ss_len, __ffs_func_bind_do_os_desc, func); - if (unlikely(ret < 0)) + if (ret < 0) goto error; } func->function.os_desc_n = @@ -3273,7 +3272,7 @@ static int ffs_func_set_alt(struct usb_function *f, if (alt != (unsigned)-1) { intf = ffs_func_revmap_intf(func, interface); - if (unlikely(intf < 0)) + if (intf < 0) return intf; } @@ -3298,7 +3297,7 @@ static int ffs_func_set_alt(struct usb_function *f, ffs->func = func; ret = ffs_func_eps_enable(func); - if (likely(ret >= 0)) + if (ret >= 0) ffs_event_add(ffs, FUNCTIONFS_ENABLE); return ret; } @@ -3340,13 +3339,13 @@ static int ffs_func_setup(struct usb_function *f, switch (creq->bRequestType & USB_RECIP_MASK) { case USB_RECIP_INTERFACE: ret = ffs_func_revmap_intf(func, le16_to_cpu(creq->wIndex)); - if (unlikely(ret < 0)) + if (ret < 0) return ret; break; case USB_RECIP_ENDPOINT: ret = ffs_func_revmap_ep(func, le16_to_cpu(creq->wIndex)); - if (unlikely(ret < 0)) + if (ret < 0) return ret; if (func->ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR) ret = func->ffs->eps_addrmap[ret]; @@ -3601,7 +3600,7 @@ static struct usb_function *ffs_alloc(struct usb_function_instance *fi) ENTER(); func = kzalloc(sizeof(*func), GFP_KERNEL); - if (unlikely(!func)) + if (!func) return ERR_PTR(-ENOMEM); func->function.name = "Function FS Gadget"; @@ -3816,7 +3815,7 @@ done: static int ffs_mutex_lock(struct mutex *mutex, unsigned nonblock) { return nonblock - ? likely(mutex_trylock(mutex)) ? 0 : -EAGAIN + ? mutex_trylock(mutex) ? 0 : -EAGAIN : mutex_lock_interruptible(mutex); } @@ -3824,14 +3823,14 @@ static char *ffs_prepare_buffer(const char __user *buf, size_t len) { char *data; - if (unlikely(!len)) + if (!len) return NULL; data = kmalloc(len, GFP_KERNEL); - if (unlikely(!data)) + if (!data) return ERR_PTR(-ENOMEM); - if (unlikely(copy_from_user(data, buf, len))) { + if (copy_from_user(data, buf, len)) { kfree(data); return ERR_PTR(-EFAULT); } From 60e998d1c6d98cd28b14a677b61278c33cc5c7df Mon Sep 17 00:00:00 2001 From: pumahsu Date: Thu, 10 Dec 2020 17:05:18 +0100 Subject: [PATCH 163/172] USB: typec: tcpm: Hard Reset after not receiving a Request PD 3.0 spec 8.3.3.2.3, A Get_Source_Cap message is sent to a UUT that is in the PE_SRC_Ready state. After sending a Source_Capabilities message, the UUT should then expect a Request message in response. When one is not received, the UUT should timeout to PE_SRC_Hard_Reset. Cc: Guenter Roeck Cc: Heikki Krogerus Cc: Badhri Jagan Sridharan Acked-by: Heikki Krogerus Signed-off-by: pumahsu Signed-off-by: Kyle Tso Signed-off-by: Will McVicker Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20201210160521.3417426-3-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index cedc6cf82d61..fa0500a03413 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -2228,6 +2228,7 @@ static int tcpm_pd_send_control(struct tcpm_port *port, static bool tcpm_send_queued_message(struct tcpm_port *port) { enum pd_msg_request queued_message; + int ret; do { queued_message = port->queued_message; @@ -2247,7 +2248,16 @@ static bool tcpm_send_queued_message(struct tcpm_port *port) tcpm_pd_send_sink_caps(port); break; case PD_MSG_DATA_SOURCE_CAP: - tcpm_pd_send_source_caps(port); + ret = tcpm_pd_send_source_caps(port); + if (ret < 0) { + tcpm_log(port, + "Unable to send src caps, ret=%d", + ret); + tcpm_set_state(port, SOFT_RESET_SEND, 0); + } else if (port->pwr_role == TYPEC_SOURCE) { + tcpm_set_state(port, HARD_RESET_SEND, + PD_T_SENDER_RESPONSE); + } break; default: break; From 301a633c1b5b2caa4c4b97a83270d4a1d60c53bf Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Thu, 10 Dec 2020 17:05:19 +0100 Subject: [PATCH 164/172] USB: typec: tcpm: Fix PR_SWAP error handling PD rev3.0 8.3.3.16.3.6 PE_PRS_SRC_SNK_Wait_Source_on State The Policy Enging Shall transition to the ErrorRecovery state when the PSSourceOnTimer times out ... Cc: Guenter Roeck Cc: Heikki Krogerus Cc: Badhri Jagan Sridharan Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Kyle Tso Signed-off-by: Will McVicker Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20201210160521.3417426-4-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index fa0500a03413..3f9edc8a9a57 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -3738,7 +3738,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, ERROR_RECOVERY, 0); break; } - tcpm_set_state_cond(port, SNK_UNATTACHED, PD_T_PS_SOURCE_ON); + tcpm_set_state(port, ERROR_RECOVERY, PD_T_PS_SOURCE_ON); break; case PR_SWAP_SRC_SNK_SINK_ON: /* Set the vbus disconnect threshold for implicit contract */ From fe79d5de77204dd946cfad76a9bec23354b1a500 Mon Sep 17 00:00:00 2001 From: Kyle Tso Date: Thu, 10 Dec 2020 17:05:20 +0100 Subject: [PATCH 165/172] USB: typec: tcpm: Add a 30ms room for tPSSourceOn in PR_SWAP TCPM state machine needs 20-25ms to enter the ErrorRecovery state after tPSSourceOn timer timeouts. Change the timer from max 480ms to 450ms to ensure that the timer complies with the Spec. In order to keep the flexibility for other usecases using tPSSourceOn, add another timer only for PR_SWAP. Cc: Guenter Roeck Cc: Heikki Krogerus Cc: Badhri Jagan Sridharan Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Kyle Tso Signed-off-by: Will McVicker Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20201210160521.3417426-5-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 2 +- include/linux/usb/pd.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 3f9edc8a9a57..36b6ce85093f 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -3738,7 +3738,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, ERROR_RECOVERY, 0); break; } - tcpm_set_state(port, ERROR_RECOVERY, PD_T_PS_SOURCE_ON); + tcpm_set_state(port, ERROR_RECOVERY, PD_T_PS_SOURCE_ON_PRS); break; case PR_SWAP_SRC_SNK_SINK_ON: /* Set the vbus disconnect threshold for implicit contract */ diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index 63a66dd5d832..bb9a782e1411 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -466,6 +466,7 @@ static inline unsigned int rdo_max_power(u32 rdo) #define PD_T_DRP_SRC 30 #define PD_T_PS_SOURCE_OFF 920 #define PD_T_PS_SOURCE_ON 480 +#define PD_T_PS_SOURCE_ON_PRS 450 /* 390 - 480ms */ #define PD_T_PS_HARD_RESET 30 #define PD_T_SRC_RECOVER 760 #define PD_T_SRC_RECOVER_MAX 1000 From ca955d3308ba111881d18bca7045aee964eac6ce Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Thu, 10 Dec 2020 17:05:21 +0100 Subject: [PATCH 166/172] USB: typec: tcpci: Add Bleed discharge to POWER_CONTROL definition "Table 4-19. POWER_CONTROL Register Definition" from tcpci spec defines BIT(3) as the control bit for bleed discharge. Cc: Guenter Roeck Cc: Heikki Krogerus Cc: Kyle Tso Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Badhri Jagan Sridharan Signed-off-by: Will McVicker Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20201210160521.3417426-6-gregkh@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci.h | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/typec/tcpm/tcpci.h b/drivers/usb/typec/tcpm/tcpci.h index 116a69c85e38..c3c7d07d9b4e 100644 --- a/drivers/usb/typec/tcpm/tcpci.h +++ b/drivers/usb/typec/tcpm/tcpci.h @@ -72,6 +72,7 @@ #define TCPC_POWER_CTRL 0x1c #define TCPC_POWER_CTRL_VCONN_ENABLE BIT(0) +#define TCPC_POWER_CTRL_BLEED_DISCHARGE BIT(3) #define TCPC_POWER_CTRL_AUTO_DISCHARGE BIT(4) #define TCPC_FAST_ROLE_SWAP_EN BIT(7) From ecf4d4310a16c32fee081924a1aebe9f4c7c0403 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Thu, 10 Dec 2020 13:16:54 -0800 Subject: [PATCH 167/172] usb: typec: Add class for plug alt mode device Add the Type C class for plug alternate mode devices which are being registered by the Type C connector class. This ensures that udev events get generated when the plug alt modes are registered. Cc: Heikki Krogerus Reviewed-by: Benson Leung Reviewed-by: Heikki Krogerus Signed-off-by: Prashant Malani Link: https://lore.kernel.org/r/20201210211653.879044-1-pmalani@chromium.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/class.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 4f6e58dfb81d..ebfd3113a9a8 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -600,6 +600,10 @@ typec_register_altmode(struct device *parent, if (is_typec_partner(parent)) alt->adev.dev.bus = &typec_bus; + /* Plug alt modes need a class to generate udev events. */ + if (is_typec_plug(parent)) + alt->adev.dev.class = typec_class; + ret = device_register(&alt->adev.dev); if (ret) { dev_err(parent, "failed to register alternate mode (%d)\n", From 3b6c3d04808965167ff19d028789bc92a4b12c8c Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Thu, 10 Dec 2020 23:11:45 -0800 Subject: [PATCH 168/172] usb: typec: tcpci: Enable bleed discharge when auto discharge is enabled Auto discharge circuits kick in only when vbus decays and reaches VBUS_SINK_DISCONNECT_THRESHOLD threshold. Enable bleed discharge to discharge vbus to VBUS_SINK_DISCONNECT_THRESHOLD upon disconnect. Reviewed-by: Heikki Krogerus Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20201211071145.2199997-1-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index af5524338a63..f676abab044b 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -725,6 +725,8 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) tcpci->tcpc.enable_auto_vbus_discharge = tcpci_enable_auto_vbus_discharge; tcpci->tcpc.set_auto_vbus_discharge_threshold = tcpci_set_auto_vbus_discharge_threshold; + regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, TCPC_POWER_CTRL_BLEED_DISCHARGE, + TCPC_POWER_CTRL_BLEED_DISCHARGE); } if (tcpci->data->vbus_vsafe0v) From 3db4c21c0f71f7a51ce5c50f0d4d3742c9ec4a65 Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Thu, 10 Dec 2020 23:19:11 -0800 Subject: [PATCH 169/172] usb: typec: tcpm: Update vbus_vsafe0v on init During init, vbus_vsafe0v does not get updated till the first connect as a sink. This causes TCPM to be stuck in SRC_ATTACH_WAIT state while booting with a sink (For instance: a headset) connected. [ 1.429168] Start toggling [ 1.439907] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected] [ 1.445242] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected] [ 53.358528] CC1: 0 -> 0, CC2: 0 -> 2 [state TOGGLING, polarity 0, connected] [ 53.358564] state change TOGGLING -> SRC_ATTACH_WAIT [rev1 NONE_AMS] Fix this by updating vbus_vsafe0v based on vbus_present status on boot. Reviewed-by: Heikki Krogerus Signed-off-by: Badhri Jagan Sridharan Link: https://lore.kernel.org/r/20201211071911.2205197-1-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 36b6ce85093f..6326a8f2e1d9 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -4804,6 +4804,24 @@ static void tcpm_init(struct tcpm_port *port) if (port->vbus_present) port->vbus_never_low = true; + /* + * 1. When vbus_present is true, voltage on VBUS is already at VSAFE5V. + * So implicitly vbus_vsafe0v = false. + * + * 2. When vbus_present is false and TCPC does NOT support querying + * vsafe0v status, then, it's best to assume vbus is at VSAFE0V i.e. + * vbus_vsafe0v is true. + * + * 3. When vbus_present is false and TCPC does support querying vsafe0v, + * then, query tcpc for vsafe0v status. + */ + if (port->vbus_present) + port->vbus_vsafe0v = false; + else if (!port->tcpc->is_vbus_vsafe0v) + port->vbus_vsafe0v = true; + else + port->vbus_vsafe0v = port->tcpc->is_vbus_vsafe0v(port->tcpc); + tcpm_set_state(port, tcpm_default_state(port), 0); if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0) From b5206275b46c30a8236feb34a1dc247fa3683d83 Mon Sep 17 00:00:00 2001 From: Zheng Yongjun Date: Fri, 11 Dec 2020 16:55:53 +0800 Subject: [PATCH 170/172] usb: typec: tcpm: convert comma to semicolon Replace a comma between expression statements by a semicolon. Reviewed-by: Heikki Krogerus Signed-off-by: Zheng Yongjun Link: https://lore.kernel.org/r/20201211085553.2982-1-zhengyongjun3@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 6326a8f2e1d9..22a85b396f69 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -5171,14 +5171,14 @@ static int devm_tcpm_psy_register(struct tcpm_port *port) snprintf(psy_name, psy_name_len, "%s%s", tcpm_psy_name_prefix, port_dev_name); port->psy_desc.name = psy_name; - port->psy_desc.type = POWER_SUPPLY_TYPE_USB, + port->psy_desc.type = POWER_SUPPLY_TYPE_USB; port->psy_desc.usb_types = tcpm_psy_usb_types; port->psy_desc.num_usb_types = ARRAY_SIZE(tcpm_psy_usb_types); - port->psy_desc.properties = tcpm_psy_props, - port->psy_desc.num_properties = ARRAY_SIZE(tcpm_psy_props), - port->psy_desc.get_property = tcpm_psy_get_prop, - port->psy_desc.set_property = tcpm_psy_set_prop, - port->psy_desc.property_is_writeable = tcpm_psy_prop_writeable, + port->psy_desc.properties = tcpm_psy_props; + port->psy_desc.num_properties = ARRAY_SIZE(tcpm_psy_props); + port->psy_desc.get_property = tcpm_psy_get_prop; + port->psy_desc.set_property = tcpm_psy_set_prop; + port->psy_desc.property_is_writeable = tcpm_psy_prop_writeable; port->usb_type = POWER_SUPPLY_USB_TYPE_C; From 2eda5b5f96418d0a006aaa7812165b72c3226b8c Mon Sep 17 00:00:00 2001 From: Zheng Yongjun Date: Fri, 11 Dec 2020 16:55:10 +0800 Subject: [PATCH 171/172] usb: ucsi: convert comma to semicolon Replace a comma between expression statements by a semicolon. Reviewed-by: Heikki Krogerus Signed-off-by: Zheng Yongjun Link: https://lore.kernel.org/r/20201211085510.2928-1-zhengyongjun3@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/psy.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/typec/ucsi/psy.c b/drivers/usb/typec/ucsi/psy.c index 571a51e16234..56bf56517f75 100644 --- a/drivers/usb/typec/ucsi/psy.c +++ b/drivers/usb/typec/ucsi/psy.c @@ -220,11 +220,11 @@ int ucsi_register_port_psy(struct ucsi_connector *con) return -ENOMEM; con->psy_desc.name = psy_name; - con->psy_desc.type = POWER_SUPPLY_TYPE_USB, + con->psy_desc.type = POWER_SUPPLY_TYPE_USB; con->psy_desc.usb_types = ucsi_psy_usb_types; con->psy_desc.num_usb_types = ARRAY_SIZE(ucsi_psy_usb_types); - con->psy_desc.properties = ucsi_psy_props, - con->psy_desc.num_properties = ARRAY_SIZE(ucsi_psy_props), + con->psy_desc.properties = ucsi_psy_props; + con->psy_desc.num_properties = ARRAY_SIZE(ucsi_psy_props); con->psy_desc.get_property = ucsi_psy_get_prop; con->psy = power_supply_register(dev, &con->psy_desc, &psy_cfg); From a256e24021bf7ceedd29fe65eb45c7adfffffad2 Mon Sep 17 00:00:00 2001 From: Zheng Yongjun Date: Fri, 11 Dec 2020 16:54:28 +0800 Subject: [PATCH 172/172] usb: phy: convert comma to semicolon Replace a comma between expression statements by a semicolon. Signed-off-by: Zheng Yongjun Link: https://lore.kernel.org/r/20201211085428.2871-1-zhengyongjun3@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-isp1301-omap.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/usb/phy/phy-isp1301-omap.c b/drivers/usb/phy/phy-isp1301-omap.c index 00506fb01bda..02bb7ddd4bd6 100644 --- a/drivers/usb/phy/phy-isp1301-omap.c +++ b/drivers/usb/phy/phy-isp1301-omap.c @@ -1571,13 +1571,13 @@ isp1301_probe(struct i2c_client *i2c, const struct i2c_device_id *id) isp->phy.dev = &i2c->dev; isp->phy.label = DRIVER_NAME; - isp->phy.set_power = isp1301_set_power, + isp->phy.set_power = isp1301_set_power; isp->phy.otg->usb_phy = &isp->phy; - isp->phy.otg->set_host = isp1301_set_host, - isp->phy.otg->set_peripheral = isp1301_set_peripheral, - isp->phy.otg->start_srp = isp1301_start_srp, - isp->phy.otg->start_hnp = isp1301_start_hnp, + isp->phy.otg->set_host = isp1301_set_host; + isp->phy.otg->set_peripheral = isp1301_set_peripheral; + isp->phy.otg->start_srp = isp1301_start_srp; + isp->phy.otg->start_hnp = isp1301_start_hnp; enable_vbus_draw(isp, 0); power_down(isp);