This driver can be used as a subdriver of another USB driver, allowing it to export a Device Managment interface consisting of a single interrupt endpoint with no dedicated USB interface. Some devices provide a Device Management function combined with a wwan function in a single USB interface having three endpoints (bulk in/out + interrupt). If the interrupt endpoint is used exclusively for DM notifications, then this driver can support that as a subdriver provided that the wwan driver calls the appropriate entry points on probe, suspend, resume, pre_reset, post_reset and disconnect. The main driver must have full control over all interface related settings, including the needs_remote_wakeup flag. A manage_power function must be provided by the main driver. A manage_power stub doing direct flag manipulation is used in normal driver mode. Signed-off-by: Bjørn Mork <bjorn@xxxxxxx> --- drivers/usb/class/cdc-wdm.c | 62 ++++++++++++++++++++++++++++++++++++++++--- include/linux/usb/cdc-wdm.h | 19 +++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 include/linux/usb/cdc-wdm.h diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 3dd050c..8945b8d 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -23,6 +23,7 @@ #include <linux/usb/cdc.h> #include <asm/byteorder.h> #include <asm/unaligned.h> +#include <linux/usb/cdc-wdm.h> /* * Version Information @@ -116,6 +117,7 @@ struct wdm_device { int rerr; struct list_head device_list; + int (*manage_power)(struct usb_interface *, int); }; static struct usb_driver wdm_driver; @@ -578,11 +580,11 @@ static int wdm_open(struct inode *inode, struct file *file) dev_err(&desc->intf->dev, "Error autopm - %d\n", rv); goto out; } - intf->needs_remote_wakeup = 1; /* using write lock to protect desc->count */ mutex_lock(&desc->wlock); if (!desc->count++) { + desc->manage_power(intf, 1); desc->werr = 0; desc->rerr = 0; rv = usb_submit_urb(desc->validity, GFP_KERNEL); @@ -616,7 +618,7 @@ static int wdm_release(struct inode *inode, struct file *file) dev_dbg(&desc->intf->dev, "wdm_release: cleanup"); kill_urbs(desc); if (!test_bit(WDM_DISCONNECTING, &desc->flags)) - desc->intf->needs_remote_wakeup = 0; + desc->manage_power(desc->intf, 0); } mutex_unlock(&wdm_mutex); return 0; @@ -663,7 +665,8 @@ static void wdm_rxwork(struct work_struct *work) /* --- hotplug --- */ -static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep, u16 bufsize) +static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep, + u16 bufsize, int (*manage_power)(struct usb_interface *, int)) { int rv = -ENOMEM; struct wdm_device *desc; @@ -748,6 +751,8 @@ static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor desc ); + desc->manage_power = manage_power; + spin_lock(&wdm_device_list_lock); list_add(&desc->device_list, &wdm_device_list); spin_unlock(&wdm_device_list_lock); @@ -764,6 +769,19 @@ err: return rv; } +static int wdm_manage_power(struct usb_interface *intf, int on) +{ + /* need autopm_get/put here to ensure the usbcore sees the new value */ + int rv = usb_autopm_get_interface(intf); + if (rv < 0) + goto err; + + intf->needs_remote_wakeup = on; + usb_autopm_put_interface(intf); +err: + return rv; +} + static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id) { int rv = -EINVAL; @@ -807,12 +825,48 @@ next_desc: goto err; ep = &iface->endpoint[0].desc; - rv = wdm_create(intf, ep, maxcom); + rv = wdm_create(intf, ep, maxcom, &wdm_manage_power); err: return rv; } +/** + * usb_cdc_wdm_register - register a WDM subdriver + * @intf: usb interface the subdriver will associate with + * @ep: interrupt endpoint to monitor for notifications + * @bufsize: maximum message size to support for read/write + * + * Create WDM usb class character device and associate it with intf + * without binding, allowing another driver to manage the interface. + * + * The subdriver will manage the given interrupt endpoint exclusively + * and will issue control requests referring to the given intf. It + * will otherwise avoid interferring, and in particular not do + * usb_set_intfdata/usb_get_intfdata on intf. + * + * The return value is a pointer to the subdriver's struct usb_driver. + * The registering driver is responsible for calling this subdriver's + * disconnect, suspend, resume, pre_reset and post_reset methods from + * its own. + */ +struct usb_driver *usb_cdc_wdm_register(struct usb_interface *intf, + struct usb_endpoint_descriptor *ep, + int bufsize, + int (*manage_power)(struct usb_interface *, int)) +{ + int rv = -EINVAL; + + rv = wdm_create(intf, ep, bufsize, manage_power); + if (rv < 0) + goto err; + + return &wdm_driver; +err: + return ERR_PTR(rv); +} +EXPORT_SYMBOL(usb_cdc_wdm_register); + static void wdm_disconnect(struct usb_interface *intf) { struct wdm_device *desc; diff --git a/include/linux/usb/cdc-wdm.h b/include/linux/usb/cdc-wdm.h new file mode 100644 index 0000000..719c332 --- /dev/null +++ b/include/linux/usb/cdc-wdm.h @@ -0,0 +1,19 @@ +/* + * USB CDC Device Management subdriver + * + * Copyright (c) 2012 Bjørn Mork <bjorn@xxxxxxx> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#ifndef __LINUX_USB_CDC_WDM_H +#define __LINUX_USB_CDC_WDM_H + +extern struct usb_driver *usb_cdc_wdm_register(struct usb_interface *intf, + struct usb_endpoint_descriptor *ep, + int bufsize, + int (*manage_power)(struct usb_interface *, int)); + +#endif /* __LINUX_USB_CDC_WDM_H */ -- 1.7.8.3 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html