Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3cc36157 authored by Bjørn Mork's avatar Bjørn Mork Committed by Greg Kroah-Hartman
Browse files

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: default avatarBjørn Mork <bjorn@mork.no>
Acked-by: default avatarOliver Neukum <oneukum@suse.de>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent b0c13860
Loading
Loading
Loading
Loading
+59 −4
Original line number Original line Diff line number Diff line
@@ -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 @@ static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor
	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 @@ static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id)
		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;
+19 −0
Original line number Original line Diff line number Diff line
/*
 * 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 */