usb: cdc-wdm: adding usb_cdc_wdm_register subdriver support
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@mork.no> Acked-by: Oliver Neukum <oneukum@suse.de> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
b0c1386080
commit
3cc3615749
|
@ -23,6 +23,7 @@
|
||||||
#include <linux/usb/cdc.h>
|
#include <linux/usb/cdc.h>
|
||||||
#include <asm/byteorder.h>
|
#include <asm/byteorder.h>
|
||||||
#include <asm/unaligned.h>
|
#include <asm/unaligned.h>
|
||||||
|
#include <linux/usb/cdc-wdm.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Version Information
|
* Version Information
|
||||||
|
@ -116,6 +117,7 @@ struct wdm_device {
|
||||||
int rerr;
|
int rerr;
|
||||||
|
|
||||||
struct list_head device_list;
|
struct list_head device_list;
|
||||||
|
int (*manage_power)(struct usb_interface *, int);
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct usb_driver wdm_driver;
|
static struct usb_driver wdm_driver;
|
||||||
|
@ -580,7 +582,6 @@ static int wdm_open(struct inode *inode, struct file *file)
|
||||||
dev_err(&desc->intf->dev, "Error autopm - %d\n", rv);
|
dev_err(&desc->intf->dev, "Error autopm - %d\n", rv);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
intf->needs_remote_wakeup = 1;
|
|
||||||
|
|
||||||
/* using write lock to protect desc->count */
|
/* using write lock to protect desc->count */
|
||||||
mutex_lock(&desc->wlock);
|
mutex_lock(&desc->wlock);
|
||||||
|
@ -597,6 +598,8 @@ static int wdm_open(struct inode *inode, struct file *file)
|
||||||
rv = 0;
|
rv = 0;
|
||||||
}
|
}
|
||||||
mutex_unlock(&desc->wlock);
|
mutex_unlock(&desc->wlock);
|
||||||
|
if (desc->count == 1)
|
||||||
|
desc->manage_power(intf, 1);
|
||||||
usb_autopm_put_interface(desc->intf);
|
usb_autopm_put_interface(desc->intf);
|
||||||
out:
|
out:
|
||||||
mutex_unlock(&wdm_mutex);
|
mutex_unlock(&wdm_mutex);
|
||||||
|
@ -618,7 +621,7 @@ static int wdm_release(struct inode *inode, struct file *file)
|
||||||
dev_dbg(&desc->intf->dev, "wdm_release: cleanup");
|
dev_dbg(&desc->intf->dev, "wdm_release: cleanup");
|
||||||
kill_urbs(desc);
|
kill_urbs(desc);
|
||||||
if (!test_bit(WDM_DISCONNECTING, &desc->flags))
|
if (!test_bit(WDM_DISCONNECTING, &desc->flags))
|
||||||
desc->intf->needs_remote_wakeup = 0;
|
desc->manage_power(desc->intf, 0);
|
||||||
}
|
}
|
||||||
mutex_unlock(&wdm_mutex);
|
mutex_unlock(&wdm_mutex);
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -665,7 +668,8 @@ static void wdm_rxwork(struct work_struct *work)
|
||||||
|
|
||||||
/* --- hotplug --- */
|
/* --- 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;
|
int rv = -ENOMEM;
|
||||||
struct wdm_device *desc;
|
struct wdm_device *desc;
|
||||||
|
@ -750,6 +754,8 @@ static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor
|
||||||
desc
|
desc
|
||||||
);
|
);
|
||||||
|
|
||||||
|
desc->manage_power = manage_power;
|
||||||
|
|
||||||
spin_lock(&wdm_device_list_lock);
|
spin_lock(&wdm_device_list_lock);
|
||||||
list_add(&desc->device_list, &wdm_device_list);
|
list_add(&desc->device_list, &wdm_device_list);
|
||||||
spin_unlock(&wdm_device_list_lock);
|
spin_unlock(&wdm_device_list_lock);
|
||||||
|
@ -766,6 +772,19 @@ err:
|
||||||
return rv;
|
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)
|
static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id)
|
||||||
{
|
{
|
||||||
int rv = -EINVAL;
|
int rv = -EINVAL;
|
||||||
|
@ -809,12 +828,48 @@ next_desc:
|
||||||
goto err;
|
goto err;
|
||||||
ep = &iface->endpoint[0].desc;
|
ep = &iface->endpoint[0].desc;
|
||||||
|
|
||||||
rv = wdm_create(intf, ep, maxcom);
|
rv = wdm_create(intf, ep, maxcom, &wdm_manage_power);
|
||||||
|
|
||||||
err:
|
err:
|
||||||
return rv;
|
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)
|
static void wdm_disconnect(struct usb_interface *intf)
|
||||||
{
|
{
|
||||||
struct wdm_device *desc;
|
struct wdm_device *desc;
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* USB CDC Device Management subdriver
|
||||||
|
*
|
||||||
|
* Copyright (c) 2012 Bjørn Mork <bjorn@mork.no>
|
||||||
|
*
|
||||||
|
* 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 */
|
Loading…
Reference in New Issue