Merge tag 'usb-7.0-rc7' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb

Pull USB/Thunderbolt fixes from Greg KH:
 "Here are a bunch of USB and Thunderbolt fixes (most all are USB) for
  7.0-rc7. More than I normally like this late in the release cycle,
  partly due to my recent travels, and partly due to people banging away
  on the USB gadget interfaces and apis more than normal (big shoutout
  to Android for getting the vendors to actually work upstream on this,
  that's a huge win overall for everyone here)

  Included in here are:
   - Small thunderbolt fix
   - new USB serial driver ids added
   - typec driver fixes
   - gadget driver fixes for some disconnect issues
   - other usb gadget driver fixes for reported problems with binding
     and unbinding devices as happens when a gadget device connects /
     disconnects from a system it is plugged into (or it switches device
     mode at a user's request, these things are complex little
     beasts...)
   - usb offload fixes (where USB audio tunnels through the controller
     while the main CPU is asleep) for when EMP spikes hit the system
     causing disconnects to happen (as often happens with static
     electricity in the winter months). This has been much reported by
     at least one vendor, and resolves the issues they have been seeing
     with this codepath. Can't wait for the "formal methods are the
     answer!" people to try to model that one properly...
   - Other small usb driver fixes for issues reported.

  All of these have been in linux-next this week, and before, with no
  reported issues, and I've personally been stressing these harder than
  normal on my systems here with no problems"

* tag 'usb-7.0-rc7' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (39 commits)
  usb: gadget: f_hid: move list and spinlock inits from bind to alloc
  usb: host: xhci-sideband: delegate offload_usage tracking to class drivers
  usb: core: use dedicated spinlock for offload state
  usb: cdns3: gadget: fix state inconsistency on gadget init failure
  usb: dwc3: imx8mp: fix memory leak on probe failure path
  usb: gadget: f_uac1_legacy: validate control request size
  usb: ulpi: fix double free in ulpi_register_interface() error path
  usb: misc: usbio: Fix URB memory leak on submit failure
  USB: core: add NO_LPM quirk for Razer Kiyo Pro webcam
  usb: cdns3: gadget: fix NULL pointer dereference in ep_queue
  usb: core: phy: avoid double use of 'usb3-phy'
  USB: serial: option: add MeiG Smart SRM825WN
  usb: gadget: f_rndis: Fix net_device lifecycle with device_move
  usb: gadget: f_subset: Fix net_device lifecycle with device_move
  usb: gadget: f_eem: Fix net_device lifecycle with device_move
  usb: gadget: f_ecm: Fix net_device lifecycle with device_move
  usb: gadget: u_ncm: Add kernel-doc comments for struct f_ncm_opts
  usb: gadget: f_rndis: Protect RNDIS options with mutex
  usb: gadget: f_subset: Fix unbalanced refcnt in geth_free
  dt-bindings: connector: add pd-disable dependency
  ...
This commit is contained in:
Linus Torvalds
2026-04-05 10:00:26 -07:00
43 changed files with 507 additions and 277 deletions

View File

@@ -301,6 +301,7 @@ properties:
maxItems: 4
dependencies:
pd-disable: [typec-power-opmode]
sink-vdos-v1: [ sink-vdos ]
sink-vdos: [ sink-vdos-v1 ]

View File

@@ -1020,7 +1020,7 @@ static bool nhi_wake_supported(struct pci_dev *pdev)
* If power rails are sustainable for wakeup from S4 this
* property is set by the BIOS.
*/
if (device_property_read_u8(&pdev->dev, "WAKE_SUPPORTED", &val))
if (!device_property_read_u8(&pdev->dev, "WAKE_SUPPORTED", &val))
return !!val;
return true;

View File

@@ -2589,6 +2589,9 @@ static int __cdns3_gadget_ep_queue(struct usb_ep *ep,
struct cdns3_request *priv_req;
int ret = 0;
if (!ep->desc)
return -ESHUTDOWN;
request->actual = 0;
request->status = -EINPROGRESS;
priv_req = to_cdns3_request(request);
@@ -3428,6 +3431,7 @@ static int __cdns3_gadget_init(struct cdns *cdns)
ret = cdns3_gadget_start(cdns);
if (ret) {
pm_runtime_put_sync(cdns->dev);
cdns_drd_gadget_off(cdns);
return ret;
}

View File

@@ -1225,6 +1225,12 @@ static int acm_probe(struct usb_interface *intf,
if (!data_interface || !control_interface)
return -ENODEV;
goto skip_normal_probe;
} else if (quirks == NO_UNION_12) {
data_interface = usb_ifnum_to_if(usb_dev, 2);
control_interface = usb_ifnum_to_if(usb_dev, 1);
if (!data_interface || !control_interface)
return -ENODEV;
goto skip_normal_probe;
}
/* normal probing*/
@@ -1748,6 +1754,9 @@ static const struct usb_device_id acm_ids[] = {
{ USB_DEVICE(0x045b, 0x024D), /* Renesas R-Car E3 USB Download mode */
.driver_info = DISABLE_ECHO, /* Don't echo banner */
},
{ USB_DEVICE(0x04b8, 0x0d12), /* EPSON HMD Com&Sens */
.driver_info = NO_UNION_12, /* union descriptor is garbage */
},
{ USB_DEVICE(0x0e8d, 0x0003), /* FIREFLY, MediaTek Inc; andrey.arapov@gmail.com */
.driver_info = NO_UNION_NORMAL, /* has no union descriptor */
},

View File

@@ -114,3 +114,4 @@ struct acm {
#define SEND_ZERO_PACKET BIT(6)
#define DISABLE_ECHO BIT(7)
#define MISSING_CAP_BRK BIT(8)
#define NO_UNION_12 BIT(9)

View File

@@ -254,6 +254,9 @@ static int usbtmc_release(struct inode *inode, struct file *file)
list_del(&file_data->file_elem);
spin_unlock_irq(&file_data->data->dev_lock);
/* flush anchored URBs */
usbtmc_draw_down(file_data);
mutex_unlock(&file_data->data->io_mutex);
kref_put(&file_data->data->kref, usbtmc_delete);

View File

@@ -331,10 +331,9 @@ struct ulpi *ulpi_register_interface(struct device *dev,
ulpi->ops = ops;
ret = ulpi_register(dev, ulpi);
if (ret) {
kfree(ulpi);
if (ret)
return ERR_PTR(ret);
}
return ulpi;
}

View File

@@ -1415,14 +1415,16 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
int status = 0;
int i = 0, n = 0;
struct usb_interface *intf;
bool offload_active = false;
if (udev->state == USB_STATE_NOTATTACHED ||
udev->state == USB_STATE_SUSPENDED)
goto done;
usb_offload_set_pm_locked(udev, true);
if (msg.event == PM_EVENT_SUSPEND && usb_offload_check(udev)) {
dev_dbg(&udev->dev, "device offloaded, skip suspend.\n");
udev->offload_at_suspend = 1;
offload_active = true;
}
/* Suspend all the interfaces and then udev itself */
@@ -1436,8 +1438,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
* interrupt urbs, allowing interrupt events to be
* handled during system suspend.
*/
if (udev->offload_at_suspend &&
intf->needs_remote_wakeup) {
if (offload_active && intf->needs_remote_wakeup) {
dev_dbg(&intf->dev,
"device offloaded, skip suspend.\n");
continue;
@@ -1452,7 +1453,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
}
}
if (status == 0) {
if (!udev->offload_at_suspend)
if (!offload_active)
status = usb_suspend_device(udev, msg);
/*
@@ -1498,7 +1499,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
*/
} else {
udev->can_submit = 0;
if (!udev->offload_at_suspend) {
if (!offload_active) {
for (i = 0; i < 16; ++i) {
usb_hcd_flush_endpoint(udev, udev->ep_out[i]);
usb_hcd_flush_endpoint(udev, udev->ep_in[i]);
@@ -1507,6 +1508,8 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
}
done:
if (status != 0)
usb_offload_set_pm_locked(udev, false);
dev_vdbg(&udev->dev, "%s: status %d\n", __func__, status);
return status;
}
@@ -1536,16 +1539,19 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg)
int status = 0;
int i;
struct usb_interface *intf;
bool offload_active = false;
if (udev->state == USB_STATE_NOTATTACHED) {
status = -ENODEV;
goto done;
}
udev->can_submit = 1;
if (msg.event == PM_EVENT_RESUME)
offload_active = usb_offload_check(udev);
/* Resume the device */
if (udev->state == USB_STATE_SUSPENDED || udev->reset_resume) {
if (!udev->offload_at_suspend)
if (!offload_active)
status = usb_resume_device(udev, msg);
else
dev_dbg(&udev->dev,
@@ -1562,8 +1568,7 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg)
* pending interrupt urbs, allowing interrupt events
* to be handled during system suspend.
*/
if (udev->offload_at_suspend &&
intf->needs_remote_wakeup) {
if (offload_active && intf->needs_remote_wakeup) {
dev_dbg(&intf->dev,
"device offloaded, skip resume.\n");
continue;
@@ -1572,11 +1577,11 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg)
udev->reset_resume);
}
}
udev->offload_at_suspend = 0;
usb_mark_last_busy(udev);
done:
dev_vdbg(&udev->dev, "%s: status %d\n", __func__, status);
usb_offload_set_pm_locked(udev, false);
if (!status)
udev->reset_resume = 0;
return status;

View File

@@ -2403,7 +2403,7 @@ void usb_hcd_resume_root_hub (struct usb_hcd *hcd)
if (hcd->rh_registered) {
pm_wakeup_event(&hcd->self.root_hub->dev, 0);
set_bit(HCD_FLAG_WAKEUP_PENDING, &hcd->flags);
queue_work(pm_wq, &hcd->wakeup_work);
queue_work(system_freezable_wq, &hcd->wakeup_work);
}
spin_unlock_irqrestore (&hcd_root_hub_lock, flags);
}

View File

@@ -25,33 +25,30 @@
*/
int usb_offload_get(struct usb_device *udev)
{
int ret;
int ret = 0;
usb_lock_device(udev);
if (udev->state == USB_STATE_NOTATTACHED) {
usb_unlock_device(udev);
if (!usb_get_dev(udev))
return -ENODEV;
if (pm_runtime_get_if_active(&udev->dev) != 1) {
ret = -EBUSY;
goto err_rpm;
}
if (udev->state == USB_STATE_SUSPENDED ||
udev->offload_at_suspend) {
usb_unlock_device(udev);
return -EBUSY;
}
spin_lock(&udev->offload_lock);
/*
* offload_usage could only be modified when the device is active, since
* it will alter the suspend flow of the device.
*/
ret = usb_autoresume_device(udev);
if (ret < 0) {
usb_unlock_device(udev);
return ret;
if (udev->offload_pm_locked) {
ret = -EAGAIN;
goto err;
}
udev->offload_usage++;
usb_autosuspend_device(udev);
usb_unlock_device(udev);
err:
spin_unlock(&udev->offload_lock);
pm_runtime_put_autosuspend(&udev->dev);
err_rpm:
usb_put_dev(udev);
return ret;
}
@@ -69,35 +66,32 @@ EXPORT_SYMBOL_GPL(usb_offload_get);
*/
int usb_offload_put(struct usb_device *udev)
{
int ret;
int ret = 0;
usb_lock_device(udev);
if (udev->state == USB_STATE_NOTATTACHED) {
usb_unlock_device(udev);
if (!usb_get_dev(udev))
return -ENODEV;
if (pm_runtime_get_if_active(&udev->dev) != 1) {
ret = -EBUSY;
goto err_rpm;
}
if (udev->state == USB_STATE_SUSPENDED ||
udev->offload_at_suspend) {
usb_unlock_device(udev);
return -EBUSY;
}
spin_lock(&udev->offload_lock);
/*
* offload_usage could only be modified when the device is active, since
* it will alter the suspend flow of the device.
*/
ret = usb_autoresume_device(udev);
if (ret < 0) {
usb_unlock_device(udev);
return ret;
if (udev->offload_pm_locked) {
ret = -EAGAIN;
goto err;
}
/* Drop the count when it wasn't 0, ignore the operation otherwise. */
if (udev->offload_usage)
udev->offload_usage--;
usb_autosuspend_device(udev);
usb_unlock_device(udev);
err:
spin_unlock(&udev->offload_lock);
pm_runtime_put_autosuspend(&udev->dev);
err_rpm:
usb_put_dev(udev);
return ret;
}
@@ -112,25 +106,47 @@ EXPORT_SYMBOL_GPL(usb_offload_put);
* management.
*
* The caller must hold @udev's device lock. In addition, the caller should
* ensure downstream usb devices are all either suspended or marked as
* "offload_at_suspend" to ensure the correctness of the return value.
* ensure the device itself and the downstream usb devices are all marked as
* "offload_pm_locked" to ensure the correctness of the return value.
*
* Returns true on any offload activity, false otherwise.
*/
bool usb_offload_check(struct usb_device *udev) __must_hold(&udev->dev->mutex)
{
struct usb_device *child;
bool active;
bool active = false;
int port1;
if (udev->offload_usage)
return true;
usb_hub_for_each_child(udev, port1, child) {
usb_lock_device(child);
active = usb_offload_check(child);
usb_unlock_device(child);
if (active)
return true;
break;
}
return !!udev->offload_usage;
return active;
}
EXPORT_SYMBOL_GPL(usb_offload_check);
/**
* usb_offload_set_pm_locked - set the PM lock state of a USB device
* @udev: the USB device to modify
* @locked: the new lock state
*
* Setting @locked to true prevents offload_usage from being modified. This
* ensures that offload activities cannot be started or stopped during critical
* power management transitions, maintaining a stable state for the duration
* of the transition.
*/
void usb_offload_set_pm_locked(struct usb_device *udev, bool locked)
{
spin_lock(&udev->offload_lock);
udev->offload_pm_locked = locked;
spin_unlock(&udev->offload_lock);
}
EXPORT_SYMBOL_GPL(usb_offload_set_pm_locked);

View File

@@ -114,7 +114,7 @@ EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc);
struct usb_phy_roothub *usb_phy_roothub_alloc_usb3_phy(struct device *dev)
{
struct usb_phy_roothub *phy_roothub;
int num_phys;
int num_phys, usb2_phy_index;
if (!IS_ENABLED(CONFIG_GENERIC_PHY))
return NULL;
@@ -124,6 +124,16 @@ struct usb_phy_roothub *usb_phy_roothub_alloc_usb3_phy(struct device *dev)
if (num_phys <= 0)
return NULL;
/*
* If 'usb2-phy' is not present, usb_phy_roothub_alloc() added
* all PHYs to the primary HCD's phy_roothub already, so skip
* adding 'usb3-phy' here to avoid double use of that.
*/
usb2_phy_index = of_property_match_string(dev->of_node, "phy-names",
"usb2-phy");
if (usb2_phy_index < 0)
return NULL;
phy_roothub = devm_kzalloc(dev, sizeof(*phy_roothub), GFP_KERNEL);
if (!phy_roothub)
return ERR_PTR(-ENOMEM);

View File

@@ -401,6 +401,7 @@ static const struct usb_device_id usb_quirk_list[] = {
/* Silicon Motion Flash Drive */
{ USB_DEVICE(0x090c, 0x1000), .driver_info = USB_QUIRK_DELAY_INIT },
{ USB_DEVICE(0x090c, 0x2000), .driver_info = USB_QUIRK_DELAY_INIT },
/* Sound Devices USBPre2 */
{ USB_DEVICE(0x0926, 0x0202), .driver_info =
@@ -492,6 +493,8 @@ static const struct usb_device_id usb_quirk_list[] = {
/* Razer - Razer Blade Keyboard */
{ USB_DEVICE(0x1532, 0x0116), .driver_info =
USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL },
/* Razer - Razer Kiyo Pro Webcam */
{ USB_DEVICE(0x1532, 0x0e05), .driver_info = USB_QUIRK_NO_LPM },
/* Lenovo ThinkPad OneLink+ Dock twin hub controllers (VIA Labs VL812) */
{ USB_DEVICE(0x17ef, 0x1018), .driver_info = USB_QUIRK_RESET_RESUME },

View File

@@ -671,6 +671,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent,
set_dev_node(&dev->dev, dev_to_node(bus->sysdev));
dev->state = USB_STATE_ATTACHED;
dev->lpm_disable_count = 1;
spin_lock_init(&dev->offload_lock);
dev->offload_usage = 0;
atomic_set(&dev->urbnum, 0);

View File

@@ -4607,7 +4607,9 @@ static int dwc2_hsotg_udc_stop(struct usb_gadget *gadget)
/* Exit clock gating when driver is stopped. */
if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_NONE &&
hsotg->bus_suspended && !hsotg->params.no_clock_gating) {
spin_lock_irqsave(&hsotg->lock, flags);
dwc2_gadget_exit_clock_gating(hsotg, 0);
spin_unlock_irqrestore(&hsotg->lock, flags);
}
/* all endpoints should be shutdown */

View File

@@ -385,8 +385,9 @@ static int dwc3_google_probe(struct platform_device *pdev)
"google,usb-cfg-csr",
ARRAY_SIZE(args), args);
if (IS_ERR(google->usb_cfg_regmap)) {
return dev_err_probe(dev, PTR_ERR(google->usb_cfg_regmap),
"invalid usb cfg csr\n");
ret = dev_err_probe(dev, PTR_ERR(google->usb_cfg_regmap),
"invalid usb cfg csr\n");
goto err_deinit_pdom;
}
google->host_cfg_offset = args[0];

View File

@@ -263,7 +263,7 @@ static int dwc3_imx8mp_probe(struct platform_device *pdev)
dwc3 = platform_get_drvdata(dwc3_imx->dwc3_pdev);
if (!dwc3) {
err = dev_err_probe(dev, -EPROBE_DEFER, "failed to get dwc3 platform data\n");
goto depopulate;
goto put_dwc3;
}
dwc3->glue_ops = &dwc3_imx_glue_ops;

View File

@@ -681,6 +681,7 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f)
struct usb_ep *ep;
struct f_ecm_opts *ecm_opts;
struct net_device *net __free(detach_gadget) = NULL;
struct usb_request *request __free(free_usb_request) = NULL;
if (!can_support_ecm(cdev->gadget))
@@ -688,18 +689,18 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f)
ecm_opts = container_of(f->fi, struct f_ecm_opts, func_inst);
mutex_lock(&ecm_opts->lock);
scoped_guard(mutex, &ecm_opts->lock)
if (ecm_opts->bind_count == 0 && !ecm_opts->bound) {
if (!device_is_registered(&ecm_opts->net->dev)) {
gether_set_gadget(ecm_opts->net, cdev->gadget);
status = gether_register_netdev(ecm_opts->net);
} else
status = gether_attach_gadget(ecm_opts->net, cdev->gadget);
gether_set_gadget(ecm_opts->net, cdev->gadget);
if (!ecm_opts->bound) {
status = gether_register_netdev(ecm_opts->net);
ecm_opts->bound = true;
}
mutex_unlock(&ecm_opts->lock);
if (status)
return status;
if (status)
return status;
net = ecm_opts->net;
}
ecm_string_defs[1].s = ecm->ethaddr;
@@ -790,6 +791,9 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f)
ecm->notify_req = no_free_ptr(request);
ecm_opts->bind_count++;
retain_and_null_ptr(net);
DBG(cdev, "CDC Ethernet: IN/%s OUT/%s NOTIFY/%s\n",
ecm->port.in_ep->name, ecm->port.out_ep->name,
ecm->notify->name);
@@ -836,7 +840,7 @@ static void ecm_free_inst(struct usb_function_instance *f)
struct f_ecm_opts *opts;
opts = container_of(f, struct f_ecm_opts, func_inst);
if (opts->bound)
if (device_is_registered(&opts->net->dev))
gether_cleanup(netdev_priv(opts->net));
else
free_netdev(opts->net);
@@ -906,9 +910,12 @@ static void ecm_free(struct usb_function *f)
static void ecm_unbind(struct usb_configuration *c, struct usb_function *f)
{
struct f_ecm *ecm = func_to_ecm(f);
struct f_ecm_opts *ecm_opts;
DBG(c->cdev, "ecm unbind\n");
ecm_opts = container_of(f->fi, struct f_ecm_opts, func_inst);
usb_free_all_descriptors(f);
if (atomic_read(&ecm->notify_count)) {
@@ -918,6 +925,10 @@ static void ecm_unbind(struct usb_configuration *c, struct usb_function *f)
kfree(ecm->notify_req->buf);
usb_ep_free_request(ecm->notify, ecm->notify_req);
ecm_opts->bind_count--;
if (ecm_opts->bind_count == 0 && !ecm_opts->bound)
gether_detach_gadget(ecm_opts->net);
}
static struct usb_function *ecm_alloc(struct usb_function_instance *fi)

View File

@@ -7,6 +7,7 @@
* Copyright (C) 2009 EF Johnson Technologies
*/
#include <linux/cleanup.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
@@ -251,24 +252,22 @@ static int eem_bind(struct usb_configuration *c, struct usb_function *f)
struct usb_ep *ep;
struct f_eem_opts *eem_opts;
struct net_device *net __free(detach_gadget) = NULL;
eem_opts = container_of(f->fi, struct f_eem_opts, func_inst);
/*
* in drivers/usb/gadget/configfs.c:configfs_composite_bind()
* configurations are bound in sequence with list_for_each_entry,
* in each configuration its functions are bound in sequence
* with list_for_each_entry, so we assume no race condition
* with regard to eem_opts->bound access
*/
if (!eem_opts->bound) {
mutex_lock(&eem_opts->lock);
gether_set_gadget(eem_opts->net, cdev->gadget);
status = gether_register_netdev(eem_opts->net);
mutex_unlock(&eem_opts->lock);
if (status)
return status;
eem_opts->bound = true;
}
scoped_guard(mutex, &eem_opts->lock)
if (eem_opts->bind_count == 0 && !eem_opts->bound) {
if (!device_is_registered(&eem_opts->net->dev)) {
gether_set_gadget(eem_opts->net, cdev->gadget);
status = gether_register_netdev(eem_opts->net);
} else
status = gether_attach_gadget(eem_opts->net, cdev->gadget);
if (status)
return status;
net = eem_opts->net;
}
us = usb_gstrings_attach(cdev, eem_strings,
ARRAY_SIZE(eem_string_defs));
@@ -279,21 +278,19 @@ static int eem_bind(struct usb_configuration *c, struct usb_function *f)
/* allocate instance-specific interface IDs */
status = usb_interface_id(c, f);
if (status < 0)
goto fail;
return status;
eem->ctrl_id = status;
eem_intf.bInterfaceNumber = status;
status = -ENODEV;
/* allocate instance-specific endpoints */
ep = usb_ep_autoconfig(cdev->gadget, &eem_fs_in_desc);
if (!ep)
goto fail;
return -ENODEV;
eem->port.in_ep = ep;
ep = usb_ep_autoconfig(cdev->gadget, &eem_fs_out_desc);
if (!ep)
goto fail;
return -ENODEV;
eem->port.out_ep = ep;
/* support all relevant hardware speeds... we expect that when
@@ -309,16 +306,14 @@ static int eem_bind(struct usb_configuration *c, struct usb_function *f)
status = usb_assign_descriptors(f, eem_fs_function, eem_hs_function,
eem_ss_function, eem_ss_function);
if (status)
goto fail;
return status;
eem_opts->bind_count++;
retain_and_null_ptr(net);
DBG(cdev, "CDC Ethernet (EEM): IN/%s OUT/%s\n",
eem->port.in_ep->name, eem->port.out_ep->name);
return 0;
fail:
ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
return status;
}
static void eem_cmd_complete(struct usb_ep *ep, struct usb_request *req)
@@ -597,7 +592,7 @@ static void eem_free_inst(struct usb_function_instance *f)
struct f_eem_opts *opts;
opts = container_of(f, struct f_eem_opts, func_inst);
if (opts->bound)
if (device_is_registered(&opts->net->dev))
gether_cleanup(netdev_priv(opts->net));
else
free_netdev(opts->net);
@@ -640,9 +635,17 @@ static void eem_free(struct usb_function *f)
static void eem_unbind(struct usb_configuration *c, struct usb_function *f)
{
struct f_eem_opts *opts;
DBG(c->cdev, "eem unbind\n");
opts = container_of(f->fi, struct f_eem_opts, func_inst);
usb_free_all_descriptors(f);
opts->bind_count--;
if (opts->bind_count == 0 && !opts->bound)
gether_detach_gadget(opts->net);
}
static struct usb_function *eem_alloc(struct usb_function_instance *fi)

View File

@@ -1262,17 +1262,8 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
if (status)
goto fail;
spin_lock_init(&hidg->write_spinlock);
hidg->write_pending = 1;
hidg->req = NULL;
spin_lock_init(&hidg->read_spinlock);
spin_lock_init(&hidg->get_report_spinlock);
init_waitqueue_head(&hidg->write_queue);
init_waitqueue_head(&hidg->read_queue);
init_waitqueue_head(&hidg->get_queue);
init_waitqueue_head(&hidg->get_id_queue);
INIT_LIST_HEAD(&hidg->completed_out_req);
INIT_LIST_HEAD(&hidg->report_list);
INIT_WORK(&hidg->work, get_report_workqueue_handler);
hidg->workqueue = alloc_workqueue("report_work",
@@ -1608,6 +1599,16 @@ static struct usb_function *hidg_alloc(struct usb_function_instance *fi)
mutex_lock(&opts->lock);
spin_lock_init(&hidg->write_spinlock);
spin_lock_init(&hidg->read_spinlock);
spin_lock_init(&hidg->get_report_spinlock);
init_waitqueue_head(&hidg->write_queue);
init_waitqueue_head(&hidg->read_queue);
init_waitqueue_head(&hidg->get_queue);
init_waitqueue_head(&hidg->get_id_queue);
INIT_LIST_HEAD(&hidg->completed_out_req);
INIT_LIST_HEAD(&hidg->report_list);
device_initialize(&hidg->dev);
hidg->dev.release = hidg_release;
hidg->dev.class = &hidg_class;

View File

@@ -11,6 +11,7 @@
/* #define VERBOSE_DEBUG */
#include <linux/cleanup.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -665,6 +666,7 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
struct f_rndis_opts *rndis_opts;
struct usb_os_desc_table *os_desc_table __free(kfree) = NULL;
struct net_device *net __free(detach_gadget) = NULL;
struct usb_request *request __free(free_usb_request) = NULL;
if (!can_support_rndis(c))
@@ -678,23 +680,22 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
return -ENOMEM;
}
rndis_iad_descriptor.bFunctionClass = rndis_opts->class;
rndis_iad_descriptor.bFunctionSubClass = rndis_opts->subclass;
rndis_iad_descriptor.bFunctionProtocol = rndis_opts->protocol;
scoped_guard(mutex, &rndis_opts->lock) {
rndis_iad_descriptor.bFunctionClass = rndis_opts->class;
rndis_iad_descriptor.bFunctionSubClass = rndis_opts->subclass;
rndis_iad_descriptor.bFunctionProtocol = rndis_opts->protocol;
/*
* in drivers/usb/gadget/configfs.c:configfs_composite_bind()
* configurations are bound in sequence with list_for_each_entry,
* in each configuration its functions are bound in sequence
* with list_for_each_entry, so we assume no race condition
* with regard to rndis_opts->bound access
*/
if (!rndis_opts->bound) {
gether_set_gadget(rndis_opts->net, cdev->gadget);
status = gether_register_netdev(rndis_opts->net);
if (status)
return status;
rndis_opts->bound = true;
if (rndis_opts->bind_count == 0 && !rndis_opts->borrowed_net) {
if (!device_is_registered(&rndis_opts->net->dev)) {
gether_set_gadget(rndis_opts->net, cdev->gadget);
status = gether_register_netdev(rndis_opts->net);
} else
status = gether_attach_gadget(rndis_opts->net, cdev->gadget);
if (status)
return status;
net = rndis_opts->net;
}
}
us = usb_gstrings_attach(cdev, rndis_strings,
@@ -793,6 +794,9 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
}
rndis->notify_req = no_free_ptr(request);
rndis_opts->bind_count++;
retain_and_null_ptr(net);
/* NOTE: all that is done without knowing or caring about
* the network link ... which is unavailable to this code
* until we're activated via set_alt().
@@ -809,11 +813,11 @@ void rndis_borrow_net(struct usb_function_instance *f, struct net_device *net)
struct f_rndis_opts *opts;
opts = container_of(f, struct f_rndis_opts, func_inst);
if (opts->bound)
if (device_is_registered(&opts->net->dev))
gether_cleanup(netdev_priv(opts->net));
else
free_netdev(opts->net);
opts->borrowed_net = opts->bound = true;
opts->borrowed_net = true;
opts->net = net;
}
EXPORT_SYMBOL_GPL(rndis_borrow_net);
@@ -871,7 +875,7 @@ static void rndis_free_inst(struct usb_function_instance *f)
opts = container_of(f, struct f_rndis_opts, func_inst);
if (!opts->borrowed_net) {
if (opts->bound)
if (device_is_registered(&opts->net->dev))
gether_cleanup(netdev_priv(opts->net));
else
free_netdev(opts->net);
@@ -940,6 +944,9 @@ static void rndis_free(struct usb_function *f)
static void rndis_unbind(struct usb_configuration *c, struct usb_function *f)
{
struct f_rndis *rndis = func_to_rndis(f);
struct f_rndis_opts *rndis_opts;
rndis_opts = container_of(f->fi, struct f_rndis_opts, func_inst);
kfree(f->os_desc_table);
f->os_desc_n = 0;
@@ -947,6 +954,10 @@ static void rndis_unbind(struct usb_configuration *c, struct usb_function *f)
kfree(rndis->notify_req->buf);
usb_ep_free_request(rndis->notify, rndis->notify_req);
rndis_opts->bind_count--;
if (rndis_opts->bind_count == 0 && !rndis_opts->borrowed_net)
gether_detach_gadget(rndis_opts->net);
}
static struct usb_function *rndis_alloc(struct usb_function_instance *fi)

View File

@@ -6,6 +6,7 @@
* Copyright (C) 2008 Nokia Corporation
*/
#include <linux/cleanup.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -298,25 +299,22 @@ geth_bind(struct usb_configuration *c, struct usb_function *f)
struct usb_ep *ep;
struct f_gether_opts *gether_opts;
struct net_device *net __free(detach_gadget) = NULL;
gether_opts = container_of(f->fi, struct f_gether_opts, func_inst);
/*
* in drivers/usb/gadget/configfs.c:configfs_composite_bind()
* configurations are bound in sequence with list_for_each_entry,
* in each configuration its functions are bound in sequence
* with list_for_each_entry, so we assume no race condition
* with regard to gether_opts->bound access
*/
if (!gether_opts->bound) {
mutex_lock(&gether_opts->lock);
gether_set_gadget(gether_opts->net, cdev->gadget);
status = gether_register_netdev(gether_opts->net);
mutex_unlock(&gether_opts->lock);
if (status)
return status;
gether_opts->bound = true;
}
scoped_guard(mutex, &gether_opts->lock)
if (gether_opts->bind_count == 0 && !gether_opts->bound) {
if (!device_is_registered(&gether_opts->net->dev)) {
gether_set_gadget(gether_opts->net, cdev->gadget);
status = gether_register_netdev(gether_opts->net);
} else
status = gether_attach_gadget(gether_opts->net, cdev->gadget);
if (status)
return status;
net = gether_opts->net;
}
us = usb_gstrings_attach(cdev, geth_strings,
ARRAY_SIZE(geth_string_defs));
@@ -329,20 +327,18 @@ geth_bind(struct usb_configuration *c, struct usb_function *f)
/* allocate instance-specific interface IDs */
status = usb_interface_id(c, f);
if (status < 0)
goto fail;
return status;
subset_data_intf.bInterfaceNumber = status;
status = -ENODEV;
/* allocate instance-specific endpoints */
ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_in_desc);
if (!ep)
goto fail;
return -ENODEV;
geth->port.in_ep = ep;
ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_out_desc);
if (!ep)
goto fail;
return -ENODEV;
geth->port.out_ep = ep;
/* support all relevant hardware speeds... we expect that when
@@ -360,21 +356,19 @@ geth_bind(struct usb_configuration *c, struct usb_function *f)
status = usb_assign_descriptors(f, fs_eth_function, hs_eth_function,
ss_eth_function, ss_eth_function);
if (status)
goto fail;
return status;
/* NOTE: all that is done without knowing or caring about
* the network link ... which is unavailable to this code
* until we're activated via set_alt().
*/
gether_opts->bind_count++;
retain_and_null_ptr(net);
DBG(cdev, "CDC Subset: IN/%s OUT/%s\n",
geth->port.in_ep->name, geth->port.out_ep->name);
return 0;
fail:
ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
return status;
}
static inline struct f_gether_opts *to_f_gether_opts(struct config_item *item)
@@ -417,7 +411,7 @@ static void geth_free_inst(struct usb_function_instance *f)
struct f_gether_opts *opts;
opts = container_of(f, struct f_gether_opts, func_inst);
if (opts->bound)
if (device_is_registered(&opts->net->dev))
gether_cleanup(netdev_priv(opts->net));
else
free_netdev(opts->net);
@@ -449,15 +443,28 @@ static struct usb_function_instance *geth_alloc_inst(void)
static void geth_free(struct usb_function *f)
{
struct f_gether *eth;
struct f_gether_opts *opts;
opts = container_of(f->fi, struct f_gether_opts, func_inst);
eth = func_to_geth(f);
scoped_guard(mutex, &opts->lock)
opts->refcnt--;
kfree(eth);
}
static void geth_unbind(struct usb_configuration *c, struct usb_function *f)
{
struct f_gether_opts *opts;
opts = container_of(f->fi, struct f_gether_opts, func_inst);
geth_string_defs[0].id = 0;
usb_free_all_descriptors(f);
opts->bind_count--;
if (opts->bind_count == 0 && !opts->bound)
gether_detach_gadget(opts->net);
}
static struct usb_function *geth_alloc(struct usb_function_instance *fi)

View File

@@ -360,19 +360,46 @@ static int f_audio_out_ep_complete(struct usb_ep *ep, struct usb_request *req)
static void f_audio_complete(struct usb_ep *ep, struct usb_request *req)
{
struct f_audio *audio = req->context;
int status = req->status;
u32 data = 0;
struct usb_ep *out_ep = audio->out_ep;
switch (status) {
case 0: /* normal completion? */
if (ep == out_ep)
switch (req->status) {
case 0:
if (ep == out_ep) {
f_audio_out_ep_complete(ep, req);
else if (audio->set_con) {
memcpy(&data, req->buf, req->length);
audio->set_con->set(audio->set_con, audio->set_cmd,
le16_to_cpu(data));
} else if (audio->set_con) {
struct usb_audio_control *con = audio->set_con;
u8 type = con->type;
u32 data;
bool valid_request = false;
switch (type) {
case UAC_FU_MUTE: {
u8 value;
if (req->actual == sizeof(value)) {
memcpy(&value, req->buf, sizeof(value));
data = value;
valid_request = true;
}
break;
}
case UAC_FU_VOLUME: {
__le16 value;
if (req->actual == sizeof(value)) {
memcpy(&value, req->buf, sizeof(value));
data = le16_to_cpu(value);
valid_request = true;
}
break;
}
}
if (valid_request)
con->set(con, audio->set_cmd, data);
else
usb_ep_set_halt(ep);
audio->set_con = NULL;
}
break;

View File

@@ -413,6 +413,12 @@ uvc_function_disconnect(struct uvc_device *uvc)
{
int ret;
guard(mutex)(&uvc->lock);
if (uvc->func_unbound) {
dev_dbg(&uvc->vdev.dev, "skipping function deactivate (unbound)\n");
return;
}
if ((ret = usb_function_deactivate(&uvc->func)) < 0)
uvcg_info(&uvc->func, "UVC disconnect failed with %d\n", ret);
}
@@ -431,6 +437,15 @@ static ssize_t function_name_show(struct device *dev,
static DEVICE_ATTR_RO(function_name);
static void uvc_vdev_release(struct video_device *vdev)
{
struct uvc_device *uvc = video_get_drvdata(vdev);
/* Signal uvc_function_unbind() that the video device has been released */
if (uvc->vdev_release_done)
complete(uvc->vdev_release_done);
}
static int
uvc_register_video(struct uvc_device *uvc)
{
@@ -443,7 +458,7 @@ uvc_register_video(struct uvc_device *uvc)
uvc->vdev.v4l2_dev->dev = &cdev->gadget->dev;
uvc->vdev.fops = &uvc_v4l2_fops;
uvc->vdev.ioctl_ops = &uvc_v4l2_ioctl_ops;
uvc->vdev.release = video_device_release_empty;
uvc->vdev.release = uvc_vdev_release;
uvc->vdev.vfl_dir = VFL_DIR_TX;
uvc->vdev.lock = &uvc->video.mutex;
uvc->vdev.device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
@@ -659,6 +674,8 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
int ret = -EINVAL;
uvcg_info(f, "%s()\n", __func__);
scoped_guard(mutex, &uvc->lock)
uvc->func_unbound = false;
opts = fi_to_f_uvc_opts(f->fi);
/* Sanity check the streaming endpoint module parameters. */
@@ -988,12 +1005,19 @@ static void uvc_free(struct usb_function *f)
static void uvc_function_unbind(struct usb_configuration *c,
struct usb_function *f)
{
DECLARE_COMPLETION_ONSTACK(vdev_release_done);
struct usb_composite_dev *cdev = c->cdev;
struct uvc_device *uvc = to_uvc(f);
struct uvc_video *video = &uvc->video;
long wait_ret = 1;
bool connected;
uvcg_info(f, "%s()\n", __func__);
scoped_guard(mutex, &uvc->lock) {
uvc->func_unbound = true;
uvc->vdev_release_done = &vdev_release_done;
connected = uvc->func_connected;
}
kthread_cancel_work_sync(&video->hw_submit);
@@ -1006,7 +1030,7 @@ static void uvc_function_unbind(struct usb_configuration *c,
* though the video device removal uevent. Allow some time for the
* application to close out before things get deleted.
*/
if (uvc->func_connected) {
if (connected) {
uvcg_dbg(f, "waiting for clean disconnect\n");
wait_ret = wait_event_interruptible_timeout(uvc->func_connected_queue,
uvc->func_connected == false, msecs_to_jiffies(500));
@@ -1017,7 +1041,10 @@ static void uvc_function_unbind(struct usb_configuration *c,
video_unregister_device(&uvc->vdev);
v4l2_device_unregister(&uvc->v4l2_dev);
if (uvc->func_connected) {
scoped_guard(mutex, &uvc->lock)
connected = uvc->func_connected;
if (connected) {
/*
* Wait for the release to occur to ensure there are no longer any
* pending operations that may cause panics when resources are cleaned
@@ -1029,6 +1056,10 @@ static void uvc_function_unbind(struct usb_configuration *c,
uvcg_dbg(f, "done waiting for release with ret: %ld\n", wait_ret);
}
/* Wait for the video device to be released */
wait_for_completion(&vdev_release_done);
uvc->vdev_release_done = NULL;
usb_ep_free_request(cdev->gadget->ep0, uvc->control_req);
kfree(uvc->control_buf);
@@ -1047,6 +1078,8 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi)
return ERR_PTR(-ENOMEM);
mutex_init(&uvc->video.mutex);
mutex_init(&uvc->lock);
uvc->func_unbound = true;
uvc->state = UVC_STATE_DISCONNECTED;
init_waitqueue_head(&uvc->func_connected_queue);
opts = fi_to_f_uvc_opts(fi);

View File

@@ -15,17 +15,26 @@
#include <linux/usb/composite.h>
/**
* struct f_ecm_opts - ECM function options
* @func_inst: USB function instance.
* @net: The net_device associated with the ECM function.
* @bound: True if the net_device is shared and pre-registered during the
* legacy composite driver's bind phase (e.g., multi.c). If false,
* the ECM function will register the net_device during its own
* bind phase.
* @bind_count: Tracks the number of configurations the ECM function is
* bound to, preventing double-registration of the @net device.
* @lock: Protects the data from concurrent access by configfs read/write
* and create symlink/remove symlink operations.
* @refcnt: Reference counter for the function instance.
*/
struct f_ecm_opts {
struct usb_function_instance func_inst;
struct net_device *net;
bool bound;
int bind_count;
/*
* Read/write access to configfs attributes is handled by configfs.
*
* This is to protect the data from concurrent access by read/write
* and create symlink/remove symlink.
*/
struct mutex lock;
int refcnt;
};

View File

@@ -15,17 +15,26 @@
#include <linux/usb/composite.h>
/**
* struct f_eem_opts - EEM function options
* @func_inst: USB function instance.
* @net: The net_device associated with the EEM function.
* @bound: True if the net_device is shared and pre-registered during the
* legacy composite driver's bind phase (e.g., multi.c). If false,
* the EEM function will register the net_device during its own
* bind phase.
* @bind_count: Tracks the number of configurations the EEM function is
* bound to, preventing double-registration of the @net device.
* @lock: Protects the data from concurrent access by configfs read/write
* and create symlink/remove symlink operations.
* @refcnt: Reference counter for the function instance.
*/
struct f_eem_opts {
struct usb_function_instance func_inst;
struct net_device *net;
bool bound;
int bind_count;
/*
* Read/write access to configfs attributes is handled by configfs.
*
* This is to protect the data from concurrent access by read/write
* and create symlink/remove symlink.
*/
struct mutex lock;
int refcnt;
};

View File

@@ -113,8 +113,10 @@ static void eth_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *p)
strscpy(p->driver, "g_ether", sizeof(p->driver));
strscpy(p->version, UETH__VERSION, sizeof(p->version));
strscpy(p->fw_version, dev->gadget->name, sizeof(p->fw_version));
strscpy(p->bus_info, dev_name(&dev->gadget->dev), sizeof(p->bus_info));
if (dev->gadget) {
strscpy(p->fw_version, dev->gadget->name, sizeof(p->fw_version));
strscpy(p->bus_info, dev_name(&dev->gadget->dev), sizeof(p->bus_info));
}
}
/* REVISIT can also support:
@@ -1223,6 +1225,11 @@ void gether_disconnect(struct gether *link)
DBG(dev, "%s\n", __func__);
spin_lock(&dev->lock);
dev->port_usb = NULL;
link->is_suspend = false;
spin_unlock(&dev->lock);
netif_stop_queue(dev->net);
netif_carrier_off(dev->net);
@@ -1260,11 +1267,6 @@ void gether_disconnect(struct gether *link)
dev->header_len = 0;
dev->unwrap = NULL;
dev->wrap = NULL;
spin_lock(&dev->lock);
dev->port_usb = NULL;
link->is_suspend = false;
spin_unlock(&dev->lock);
}
EXPORT_SYMBOL_GPL(gether_disconnect);

View File

@@ -15,17 +15,25 @@
#include <linux/usb/composite.h>
/**
* struct f_gether_opts - subset function options
* @func_inst: USB function instance.
* @net: The net_device associated with the subset function.
* @bound: True if the net_device is shared and pre-registered during the
* legacy composite driver's bind phase (e.g., multi.c). If false,
* the subset function will register the net_device during its own
* bind phase.
* @bind_count: Tracks the number of configurations the subset function is
* bound to, preventing double-registration of the @net device.
* @lock: Protects the data from concurrent access by configfs read/write
* and create symlink/remove symlink operations.
* @refcnt: Reference counter for the function instance.
*/
struct f_gether_opts {
struct usb_function_instance func_inst;
struct net_device *net;
bool bound;
/*
* Read/write access to configfs attributes is handled by configfs.
*
* This is to protect the data from concurrent access by read/write
* and create symlink/remove symlink.
*/
int bind_count;
struct mutex lock;
int refcnt;
};

View File

@@ -15,6 +15,20 @@
#include <linux/usb/composite.h>
/**
* struct f_ncm_opts - NCM function options
* @func_inst: USB function instance.
* @net: The net_device associated with the NCM function.
* @bind_count: Tracks the number of configurations the NCM function is
* bound to, preventing double-registration of the @net device.
* @ncm_interf_group: ConfigFS group for NCM interface.
* @ncm_os_desc: USB OS descriptor for NCM.
* @ncm_ext_compat_id: Extended compatibility ID.
* @lock: Protects the data from concurrent access by configfs read/write
* and create symlink/remove symlink operations.
* @refcnt: Reference counter for the function instance.
* @max_segment_size: Maximum segment size.
*/
struct f_ncm_opts {
struct usb_function_instance func_inst;
struct net_device *net;
@@ -23,12 +37,7 @@ struct f_ncm_opts {
struct config_group *ncm_interf_group;
struct usb_os_desc ncm_os_desc;
char ncm_ext_compat_id[16];
/*
* Read/write access to configfs attributes is handled by configfs.
*
* This is to protect the data from concurrent access by read/write
* and create symlink/remove symlink.
*/
struct mutex lock;
int refcnt;

View File

@@ -15,12 +15,34 @@
#include <linux/usb/composite.h>
/**
* struct f_rndis_opts - RNDIS function options
* @func_inst: USB function instance.
* @vendor_id: Vendor ID.
* @manufacturer: Manufacturer string.
* @net: The net_device associated with the RNDIS function.
* @bind_count: Tracks the number of configurations the RNDIS function is
* bound to, preventing double-registration of the @net device.
* @borrowed_net: True if the net_device is shared and pre-registered during
* the legacy composite driver's bind phase (e.g., multi.c).
* If false, the RNDIS function will register the net_device
* during its own bind phase.
* @rndis_interf_group: ConfigFS group for RNDIS interface.
* @rndis_os_desc: USB OS descriptor for RNDIS.
* @rndis_ext_compat_id: Extended compatibility ID.
* @class: USB class.
* @subclass: USB subclass.
* @protocol: USB protocol.
* @lock: Protects the data from concurrent access by configfs read/write
* and create symlink/remove symlink operations.
* @refcnt: Reference counter for the function instance.
*/
struct f_rndis_opts {
struct usb_function_instance func_inst;
u32 vendor_id;
const char *manufacturer;
struct net_device *net;
bool bound;
int bind_count;
bool borrowed_net;
struct config_group *rndis_interf_group;
@@ -30,13 +52,6 @@ struct f_rndis_opts {
u8 class;
u8 subclass;
u8 protocol;
/*
* Read/write access to configfs attributes is handled by configfs.
*
* This is to protect the data from concurrent access by read/write
* and create symlink/remove symlink.
*/
struct mutex lock;
int refcnt;
};

View File

@@ -155,6 +155,9 @@ struct uvc_device {
enum uvc_state state;
struct usb_function func;
struct uvc_video video;
struct completion *vdev_release_done;
struct mutex lock; /* protects func_unbound and func_connected */
bool func_unbound;
bool func_connected;
wait_queue_head_t func_connected_queue;

View File

@@ -574,6 +574,8 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
if (sub->type < UVC_EVENT_FIRST || sub->type > UVC_EVENT_LAST)
return -EINVAL;
guard(mutex)(&uvc->lock);
if (sub->type == UVC_EVENT_SETUP && uvc->func_connected)
return -EBUSY;
@@ -595,7 +597,8 @@ static void uvc_v4l2_disable(struct uvc_device *uvc)
uvc_function_disconnect(uvc);
uvcg_video_disable(&uvc->video);
uvcg_free_buffers(&uvc->video.queue);
uvc->func_connected = false;
scoped_guard(mutex, &uvc->lock)
uvc->func_connected = false;
wake_up_interruptible(&uvc->func_connected_queue);
}

View File

@@ -462,8 +462,13 @@ static void set_link_state(struct dummy_hcd *dum_hcd)
/* Report reset and disconnect events to the driver */
if (dum->ints_enabled && (disconnect || reset)) {
stop_activity(dum);
++dum->callback_usage;
/*
* stop_activity() can drop dum->lock, so it must
* not come between the dum->ints_enabled test
* and the ++dum->callback_usage.
*/
stop_activity(dum);
spin_unlock(&dum->lock);
if (reset)
usb_gadget_udc_reset(&dum->gadget, dum->driver);
@@ -908,21 +913,6 @@ static int dummy_pullup(struct usb_gadget *_gadget, int value)
spin_lock_irqsave(&dum->lock, flags);
dum->pullup = (value != 0);
set_link_state(dum_hcd);
if (value == 0) {
/*
* Emulate synchronize_irq(): wait for callbacks to finish.
* This seems to be the best place to emulate the call to
* synchronize_irq() that's in usb_gadget_remove_driver().
* Doing it in dummy_udc_stop() would be too late since it
* is called after the unbind callback and unbind shouldn't
* be invoked until all the other callbacks are finished.
*/
while (dum->callback_usage > 0) {
spin_unlock_irqrestore(&dum->lock, flags);
usleep_range(1000, 2000);
spin_lock_irqsave(&dum->lock, flags);
}
}
spin_unlock_irqrestore(&dum->lock, flags);
usb_hcd_poll_rh_status(dummy_hcd_to_hcd(dum_hcd));
@@ -945,6 +935,20 @@ static void dummy_udc_async_callbacks(struct usb_gadget *_gadget, bool enable)
spin_lock_irq(&dum->lock);
dum->ints_enabled = enable;
if (!enable) {
/*
* Emulate synchronize_irq(): wait for callbacks to finish.
* This has to happen after emulated interrupts are disabled
* (dum->ints_enabled is clear) and before the unbind callback,
* just like the call to synchronize_irq() in
* gadget/udc/core:gadget_unbind_driver().
*/
while (dum->callback_usage > 0) {
spin_unlock_irq(&dum->lock);
usleep_range(1000, 2000);
spin_lock_irq(&dum->lock);
}
}
spin_unlock_irq(&dum->lock);
}
@@ -1534,6 +1538,12 @@ top:
/* rescan to continue with any other queued i/o */
if (rescan)
goto top;
/* request not fully transferred; stop iterating to
* preserve data ordering across queued requests.
*/
if (req->req.actual < req->req.length)
break;
}
return sent;
}

View File

@@ -31,8 +31,8 @@ static inline void ehci_brcm_wait_for_sof(struct ehci_hcd *ehci, u32 delay)
int res;
/* Wait for next microframe (every 125 usecs) */
res = readl_relaxed_poll_timeout(&ehci->regs->frame_index, val,
val != frame_idx, 1, 130);
res = readl_relaxed_poll_timeout_atomic(&ehci->regs->frame_index,
val, val != frame_idx, 1, 130);
if (res)
ehci_err(ehci, "Error waiting for SOF\n");
udelay(delay);

View File

@@ -93,8 +93,6 @@ __xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct xhci_virt_ep *e
static void
__xhci_sideband_remove_interrupter(struct xhci_sideband *sb)
{
struct usb_device *udev;
lockdep_assert_held(&sb->mutex);
if (!sb->ir)
@@ -102,10 +100,6 @@ __xhci_sideband_remove_interrupter(struct xhci_sideband *sb)
xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir);
sb->ir = NULL;
udev = sb->vdev->udev;
if (udev->state != USB_STATE_NOTATTACHED)
usb_offload_put(udev);
}
/* sideband api functions */
@@ -291,8 +285,8 @@ EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer);
* Allow other drivers, such as usb controller driver, to check if there are
* any sideband activity on the host controller. This information could be used
* for power management or other forms of resource management. The caller should
* ensure downstream usb devices are all either suspended or marked as
* "offload_at_suspend" to ensure the correctness of the return value.
* ensure downstream usb devices are all marked as "offload_pm_locked" to ensure
* the correctness of the return value.
*
* Returns true on any active sideband existence, false otherwise.
*/
@@ -328,9 +322,6 @@ int
xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg,
bool ip_autoclear, u32 imod_interval, int intr_num)
{
int ret = 0;
struct usb_device *udev;
if (!sb || !sb->xhci)
return -ENODEV;
@@ -348,12 +339,9 @@ xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg,
if (!sb->ir)
return -ENOMEM;
udev = sb->vdev->udev;
ret = usb_offload_get(udev);
sb->ir->ip_autoclear = ip_autoclear;
return ret;
return 0;
}
EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter);

View File

@@ -614,8 +614,10 @@ static int usbio_probe(struct usb_interface *intf, const struct usb_device_id *i
usb_fill_bulk_urb(usbio->urb, udev, usbio->rx_pipe, usbio->rxbuf,
usbio->rxbuf_len, usbio_bulk_recv, usbio);
ret = usb_submit_urb(usbio->urb, GFP_KERNEL);
if (ret)
return dev_err_probe(dev, ret, "Submitting usb urb\n");
if (ret) {
dev_err_probe(dev, ret, "Submitting usb urb\n");
goto err_free_urb;
}
mutex_lock(&usbio->ctrl_mutex);
@@ -663,6 +665,7 @@ static int usbio_probe(struct usb_interface *intf, const struct usb_device_id *i
err_unlock:
mutex_unlock(&usbio->ctrl_mutex);
usb_kill_urb(usbio->urb);
err_free_urb:
usb_free_urb(usbio->urb);
return ret;

View File

@@ -73,6 +73,7 @@ static const struct usb_device_id edgeport_4port_id_table[] = {
{ USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_22I) },
{ USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_412_4) },
{ USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_COMPATIBLE) },
{ USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_BLACKBOX_IC135A) },
{ }
};
@@ -121,6 +122,7 @@ static const struct usb_device_id id_table_combined[] = {
{ USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8R) },
{ USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_8RR) },
{ USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_EDGEPORT_412_8) },
{ USB_DEVICE(USB_VENDOR_ID_ION, ION_DEVICE_ID_BLACKBOX_IC135A) },
{ USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0202) },
{ USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0203) },
{ USB_DEVICE(USB_VENDOR_ID_NCR, NCR_DEVICE_ID_EPIC_0310) },
@@ -470,6 +472,7 @@ static void get_product_info(struct edgeport_serial *edge_serial)
case ION_DEVICE_ID_EDGEPORT_2_DIN:
case ION_DEVICE_ID_EDGEPORT_4_DIN:
case ION_DEVICE_ID_EDGEPORT_16_DUAL_CPU:
case ION_DEVICE_ID_BLACKBOX_IC135A:
product_info->IsRS232 = 1;
break;

View File

@@ -211,6 +211,7 @@
//
// Definitions for other product IDs
#define ION_DEVICE_ID_BLACKBOX_IC135A 0x0801 // OEM device (rebranded Edgeport/4)
#define ION_DEVICE_ID_MT4X56USB 0x1403 // OEM device
#define ION_DEVICE_ID_E5805A 0x1A01 // OEM device (rebranded Edgeport/4)

View File

@@ -2441,6 +2441,9 @@ static const struct usb_device_id option_ids[] = {
{ USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d22, 0xff, 0xff, 0x30) }, /* MeiG Smart SRM815 and SRM825L */
{ USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d22, 0xff, 0xff, 0x40) }, /* MeiG Smart SRM825L */
{ USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d22, 0xff, 0xff, 0x60) }, /* MeiG Smart SRM825L */
{ USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d38, 0xff, 0xff, 0x30) }, /* MeiG Smart SRM825WN (Diag) */
{ USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d38, 0xff, 0xff, 0x40) }, /* MeiG Smart SRM825WN (AT) */
{ USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d38, 0xff, 0xff, 0x60) }, /* MeiG Smart SRM825WN (NMEA) */
{ USB_DEVICE_INTERFACE_CLASS(0x2df3, 0x9d03, 0xff) }, /* LongSung M5710 */
{ USB_DEVICE_INTERFACE_CLASS(0x305a, 0x1404, 0xff) }, /* GosunCn GM500 RNDIS */
{ USB_DEVICE_INTERFACE_CLASS(0x305a, 0x1405, 0xff) }, /* GosunCn GM500 MBIM */
@@ -2461,6 +2464,7 @@ static const struct usb_device_id option_ids[] = {
{ USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x0302, 0xff) }, /* Rolling RW101R-GL (laptop MBIM) */
{ USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x0802, 0xff), /* Rolling RW350-GL (laptop MBIM) */
.driver_info = RSVD(5) },
{ USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x1003, 0xff) }, /* Rolling RW135R-GL (laptop MBIM) */
{ USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0100, 0xff, 0xff, 0x30) }, /* NetPrisma LCUK54-WWD for Global */
{ USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0100, 0xff, 0x00, 0x40) },
{ USB_DEVICE_AND_INTERFACE_INFO(0x3731, 0x0100, 0xff, 0xff, 0x40) },

View File

@@ -39,28 +39,7 @@ static bool tbt_ready(struct typec_altmode *alt);
static int tbt_enter_mode(struct tbt_altmode *tbt)
{
struct typec_altmode *plug = tbt->plug[TYPEC_PLUG_SOP_P];
u32 vdo;
vdo = tbt->alt->vdo & (TBT_VENDOR_SPECIFIC_B0 | TBT_VENDOR_SPECIFIC_B1);
vdo |= tbt->alt->vdo & TBT_INTEL_SPECIFIC_B0;
vdo |= TBT_MODE;
if (plug) {
if (typec_cable_is_active(tbt->cable))
vdo |= TBT_ENTER_MODE_ACTIVE_CABLE;
vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_SPEED(plug->vdo));
vdo |= plug->vdo & TBT_CABLE_ROUNDED;
vdo |= plug->vdo & TBT_CABLE_OPTICAL;
vdo |= plug->vdo & TBT_CABLE_RETIMER;
vdo |= plug->vdo & TBT_CABLE_LINK_TRAINING;
} else {
vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_USB3_PASSIVE);
}
tbt->enter_vdo = vdo;
return typec_altmode_enter(tbt->alt, &vdo);
return typec_altmode_enter(tbt->alt, &tbt->enter_vdo);
}
static void tbt_altmode_work(struct work_struct *work)
@@ -337,6 +316,7 @@ static bool tbt_ready(struct typec_altmode *alt)
{
struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
struct typec_altmode *plug;
u32 vdo;
if (tbt->cable)
return true;
@@ -364,6 +344,26 @@ static bool tbt_ready(struct typec_altmode *alt)
tbt->plug[i] = plug;
}
vdo = tbt->alt->vdo & (TBT_VENDOR_SPECIFIC_B0 | TBT_VENDOR_SPECIFIC_B1);
vdo |= tbt->alt->vdo & TBT_INTEL_SPECIFIC_B0;
vdo |= TBT_MODE;
plug = tbt->plug[TYPEC_PLUG_SOP_P];
if (plug) {
if (typec_cable_is_active(tbt->cable))
vdo |= TBT_ENTER_MODE_ACTIVE_CABLE;
vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_SPEED(plug->vdo));
vdo |= plug->vdo & TBT_CABLE_ROUNDED;
vdo |= plug->vdo & TBT_CABLE_OPTICAL;
vdo |= plug->vdo & TBT_CABLE_RETIMER;
vdo |= plug->vdo & TBT_CABLE_LINK_TRAINING;
} else {
vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_USB3_PASSIVE);
}
tbt->enter_vdo = vdo;
return true;
}

View File

@@ -686,10 +686,6 @@ typec_register_altmode(struct device *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",

View File

@@ -43,8 +43,13 @@ void ucsi_notify_common(struct ucsi *ucsi, u32 cci)
if (cci & UCSI_CCI_BUSY)
return;
if (UCSI_CCI_CONNECTOR(cci))
ucsi_connector_change(ucsi, UCSI_CCI_CONNECTOR(cci));
if (UCSI_CCI_CONNECTOR(cci)) {
if (UCSI_CCI_CONNECTOR(cci) <= ucsi->cap.num_connectors)
ucsi_connector_change(ucsi, UCSI_CCI_CONNECTOR(cci));
else
dev_err(ucsi->dev, "bogus connector number in CCI: %lu\n",
UCSI_CCI_CONNECTOR(cci));
}
if (cci & UCSI_CCI_ACK_COMPLETE &&
test_and_clear_bit(ACK_PENDING, &ucsi->flags))

View File

@@ -21,6 +21,7 @@
#include <linux/completion.h> /* for struct completion */
#include <linux/sched.h> /* for current && schedule_timeout */
#include <linux/mutex.h> /* for struct mutex */
#include <linux/spinlock.h> /* for spinlock_t */
#include <linux/pm_runtime.h> /* for runtime PM */
struct usb_device;
@@ -636,8 +637,9 @@ struct usb3_lpm_parameters {
* @do_remote_wakeup: remote wakeup should be enabled
* @reset_resume: needs reset instead of resume
* @port_is_suspended: the upstream port is suspended (L2 or U3)
* @offload_at_suspend: offload activities during suspend is enabled.
* @offload_pm_locked: prevents offload_usage changes during PM transitions.
* @offload_usage: number of offload activities happening on this usb device.
* @offload_lock: protects offload_usage and offload_pm_locked
* @slot_id: Slot ID assigned by xHCI
* @l1_params: best effor service latency for USB2 L1 LPM state, and L1 timeout.
* @u1_params: exit latencies for USB3 U1 LPM state, and hub-initiated timeout.
@@ -726,8 +728,9 @@ struct usb_device {
unsigned do_remote_wakeup:1;
unsigned reset_resume:1;
unsigned port_is_suspended:1;
unsigned offload_at_suspend:1;
unsigned offload_pm_locked:1;
int offload_usage;
spinlock_t offload_lock;
enum usb_link_tunnel_mode tunnel_mode;
struct device_link *usb4_link;
@@ -849,6 +852,7 @@ static inline void usb_mark_last_busy(struct usb_device *udev)
int usb_offload_get(struct usb_device *udev);
int usb_offload_put(struct usb_device *udev);
bool usb_offload_check(struct usb_device *udev);
void usb_offload_set_pm_locked(struct usb_device *udev, bool locked);
#else
static inline int usb_offload_get(struct usb_device *udev)
@@ -857,6 +861,8 @@ static inline int usb_offload_put(struct usb_device *udev)
{ return 0; }
static inline bool usb_offload_check(struct usb_device *udev)
{ return false; }
static inline void usb_offload_set_pm_locked(struct usb_device *udev, bool locked)
{ }
#endif
extern int usb_disable_lpm(struct usb_device *udev);

View File

@@ -699,6 +699,7 @@ static void uaudio_event_ring_cleanup_free(struct uaudio_dev *dev)
uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE,
PAGE_SIZE);
xhci_sideband_remove_interrupter(uadev[dev->chip->card->number].sb);
usb_offload_put(dev->udev);
}
}
@@ -1182,12 +1183,16 @@ static int uaudio_event_ring_setup(struct snd_usb_substream *subs,
dma_coherent = dev_is_dma_coherent(subs->dev->bus->sysdev);
er_pa = 0;
ret = usb_offload_get(subs->dev);
if (ret < 0)
goto exit;
/* event ring */
ret = xhci_sideband_create_interrupter(uadev[card_num].sb, 1, false,
0, uaudio_qdev->data->intr_num);
if (ret < 0) {
dev_err(&subs->dev->dev, "failed to fetch interrupter\n");
goto exit;
goto put_offload;
}
sgt = xhci_sideband_get_event_buffer(uadev[card_num].sb);
@@ -1219,6 +1224,8 @@ clear_pa:
mem_info->dma = 0;
remove_interrupter:
xhci_sideband_remove_interrupter(uadev[card_num].sb);
put_offload:
usb_offload_put(subs->dev);
exit:
return ret;
}
@@ -1482,6 +1489,7 @@ unmap_er:
uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE, PAGE_SIZE);
free_sec_ring:
xhci_sideband_remove_interrupter(uadev[card_num].sb);
usb_offload_put(subs->dev);
drop_sync_ep:
if (subs->sync_endpoint) {
uaudio_iommu_unmap(MEM_XFER_RING,