ANDROID: usb: gadget: configfs: Add Uevent to notify userspace

Android userspace UsbDeviceManager relies on the uevents generated
by the composition driver to generate user notifications. Therefore,
uevents will be generated whenever USB changes its state. This patch
also add "state" attribute to android_device to show USB state. In
addition, this patch adds android_create_function_device to create child
function devices for USB gadget functions as well as
android_remove_function_device to remove them. Android UsbDeviceManager
relies on communicating to the devices created by the gadget functions
to implement functions such as midi.

Bug: 120441124
Bug: 317149848
Bug: 352124133
Change-Id: I471772a2514568dd7f2b2a0316a5f018b3b378ed
Signed-off-by: Neill Kapron <nkapron@google.com>
Signed-off-by: Elson Roy Serrao <quic_eserrao@quicinc.com>
This commit is contained in:
Neill Kapron
2024-02-01 08:43:46 +00:00
committed by Elson Roy Serrao
parent ca65f513e7
commit 957b40b3b7
11 changed files with 599 additions and 1 deletions
@@ -0,0 +1,16 @@
Android USB devices (eg. /sys/class/android_usb/android0/)
What: /sys/class/android_usb/<android_device>/state
Date: Feb 2024
Contact: Neill Kapron <nkapron@google.com>
Description:
The state of the USB connection. This attribute is likely
redundant with the /sys/class/UDC/state attribute, and should
be deprecated/removed when userspace can be refactored.
Change on the state will also generate uevent KOBJ_CHANGE on
the port with the new state included in the message as
"USB_STATE=<STATE>". Note this is not the correct usage of
uevents, but necessary due to the requirement to maintaine
userspace API compatibility.
Valid values: CONNECTED, DISCONNECTED, CONFIGURED
+1
View File
@@ -496,6 +496,7 @@ CONFIG_USB_SERIAL=m
CONFIG_USB_SERIAL_FTDI_SIO=m
CONFIG_USB_GADGET=y
CONFIG_USB_CONFIGFS=y
CONFIG_ANDROID_USB_CONFIGFS_UEVENT=y
CONFIG_USB_CONFIGFS_SERIAL=y
CONFIG_USB_CONFIGFS_ACM=y
CONFIG_USB_CONFIGFS_NCM=y
+1
View File
@@ -465,6 +465,7 @@ CONFIG_USB_SERIAL=m
CONFIG_USB_SERIAL_FTDI_SIO=m
CONFIG_USB_GADGET=y
CONFIG_USB_CONFIGFS=y
CONFIG_ANDROID_USB_CONFIGFS_UEVENT=y
CONFIG_USB_CONFIGFS_SERIAL=y
CONFIG_USB_CONFIGFS_ACM=y
CONFIG_USB_CONFIGFS_NCM=y
+11
View File
@@ -235,6 +235,17 @@ config USB_CONFIGFS
appropriate symbolic links.
For more information see Documentation/usb/gadget_configfs.rst.
config ANDROID_USB_CONFIGFS_UEVENT
bool "Uevent notification of Gadget State"
depends on USB_CONFIGFS
help
Enable uevent notifications to userspace when gadget state changes.
The gadget can be in any of the following three states:
"CONNECTED", "DISCONNECTED" or "CONFIGURED".
Additionally, selecting this will create the android_usb class of
devices, including a "state" attribute for the android_device which
shows the gadget state.
config USB_CONFIGFS_SERIAL
bool "Generic serial bulk in/out"
depends on USB_CONFIGFS
+1
View File
@@ -8,5 +8,6 @@ subdir-ccflags-$(CONFIG_USB_GADGET_VERBOSE) += -DVERBOSE_DEBUG
obj-$(CONFIG_USB_LIBCOMPOSITE) += libcomposite.o
libcomposite-y := usbstring.o config.o epautoconf.o
libcomposite-y += composite.o functions.o configfs.o u_f.o
libcomposite-$(CONFIG_ANDROID_USB_CONFIGFS_UEVENT) += android_configfs_uevent.o
obj-$(CONFIG_USB_GADGET) += udc/ function/ legacy/
@@ -0,0 +1,328 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2011-2024 Google LLC
*/
#include "android_configfs_uevent.h"
#include <linux/device.h>
#include <linux/device/class.h>
#include <linux/err.h>
#include <linux/kdev_t.h>
#include <linux/spinlock.h>
static struct android_uevent_opts *android_opts;
static DEFINE_SPINLOCK(opts_lock);
static DEFINE_IDA(android_ida);
static void android_work(struct work_struct *data)
{
struct android_uevent_opts *opts = container_of(data,
struct android_uevent_opts, work);
char *disconnected_strs[2] = { "USB_STATE=DISCONNECTED", NULL };
char *connected_strs[2] = { "USB_STATE=CONNECTED", NULL };
char *configured_strs[2] = { "USB_STATE=CONFIGURED", NULL };
unsigned long flags;
bool disconnected = false;
bool connected = false;
bool configured = false;
bool uevent_sent = false;
struct device *dev;
/*
* I believe locking is important due to the fact that we are checking
* several conditions here, and if the state changes after checking one
* we could potentially drop a uevent to userspace. Additionally, we
* want to prevent teardown until after events are sent.
*/
spin_lock_irqsave(&opts_lock, flags);
/*
* If the device does not exist, it means we were torn down after
* scheduling this work, but before the work ran, so return to prevent
* use after free.
*/
if (!opts->dev) {
spin_unlock_irqrestore(&opts_lock, flags);
return;
}
/*
* Cache the dev pointer in the locked area incase it gets cleared by
* android_device_destroy() after we release the lock. The call to
* flush_work in the cleanup path ensures we finish our work prior to
* destroying the dev which we have cached the pointer to. Ideally,
* this would be handled differently (using reference counting), but
* for now this should work.
*/
dev = opts->dev;
if (opts->connected != opts->sw_connected) {
if (opts->connected)
connected = true;
else
disconnected = true;
opts->sw_connected = opts->connected;
}
if (opts->configured)
configured = true;
spin_unlock_irqrestore(&opts_lock, flags);
/*
* This is an abuse of uevents, however the android userspace parses
* the uevent string for information instead of reading the state from
* sysfs entries. This is one of several things about this driver which
* would need to change to upstream it. In an attempt to keep the
* exising userspace api unmodified until either an upstream solution
* is implemented or this functionality is otherwise replaced, leave
* the pre-existing logic in place.
*/
if (connected) {
if (kobject_uevent_env(&dev->kobj, KOBJ_CHANGE,
connected_strs)) {
dev_err(dev, "Failed to send connected uevent\n");
} else {
dev_dbg(dev, "sent uevent %s\n", connected_strs[0]);
uevent_sent = true;
}
}
if (configured) {
if (kobject_uevent_env(&dev->kobj, KOBJ_CHANGE,
configured_strs)) {
dev_err(dev, "Failed to send configured uevent\n");
} else {
dev_dbg(dev, "sent uevent %s\n", configured_strs[0]);
uevent_sent = true;
}
}
if (disconnected) {
if (kobject_uevent_env(&dev->kobj, KOBJ_CHANGE,
disconnected_strs)) {
dev_err(dev, "Failed to send disconnected uevent\n");
} else {
dev_dbg(dev, "sent uevent %s\n", disconnected_strs[0]);
uevent_sent = true;
}
}
if (!uevent_sent) {
/*
* This is an odd case, but not necessarily an error- the state
* of the device may have changed since the work was scheduled,
* and if the state changed, there is likely another scheduled
* work which will send a uevent.
*/
dev_dbg(dev, "did not send uevent\n");
}
}
static ssize_t state_show(struct device *pdev,
struct device_attribute *attr,
char *buf)
{
struct android_uevent_opts *opts = dev_get_drvdata(pdev);
char *state = "DISCONNECTED";
if (opts->configured)
state = "CONFIGURED";
else if (opts->connected)
state = "CONNECTED";
return sysfs_emit(buf, "%s\n", state);
}
static DEVICE_ATTR_RO(state);
static struct attribute *android_usb_attrs[] = {
&dev_attr_state.attr,
NULL,
};
ATTRIBUTE_GROUPS(android_usb);
static struct class android_usb_class = {
.name = "android_usb",
.dev_groups = android_usb_groups,
};
int android_class_create(void)
{
return class_register(&android_usb_class);
}
EXPORT_SYMBOL_GPL(android_class_create);
void android_class_destroy(void)
{
class_unregister(&android_usb_class);
}
EXPORT_SYMBOL_GPL(android_class_destroy);
int android_device_create(struct android_uevent_opts *opts)
{
unsigned long flags;
struct device *dev;
spin_lock_irqsave(&opts_lock, flags);
INIT_WORK(&opts->work, android_work);
opts->device_id = ida_alloc(&android_ida, GFP_ATOMIC);
//Unlock prior to calling device_create() since it may sleep
spin_unlock_irqrestore(&opts_lock, flags);
if (opts->device_id < 0)
return opts->device_id;
dev = device_create(&android_usb_class, NULL, MKDEV(0, 0),
opts, "android%d", opts->device_id);
spin_lock_irqsave(&opts_lock, flags);
if (IS_ERR(dev)) {
ida_free(&android_ida, opts->device_id);
opts->device_id = -1;
spin_unlock_irqrestore(&opts_lock, flags);
return PTR_ERR(dev);
}
opts->dev = dev;
ida_init(&opts->function_ida);
if (!android_opts)
android_opts = opts;
spin_unlock_irqrestore(&opts_lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(android_device_create);
void android_device_destroy(struct android_uevent_opts *opts)
{
unsigned long flags;
struct device *dev;
/*
* This scheme is used to safely cleanup any remaining work. Once
* opts->dev is set to NULL, any newly scheduled work will return
* after getting the lock and checking for NULL. Any currently
* running work finishes with the flush_work (the worker caches
* opts->dev so it can continue), before we free the device.
*
* Ideally, this cleanup would be handled via reference counting, but
* there are nuances around device destroy (or the fact that we are
* currently statically allocating opts) which prevent this from
* being implemented without a significant refactor.
*/
spin_lock_irqsave(&opts_lock, flags);
dev = opts->dev;
opts->dev = NULL;
spin_unlock_irqrestore(&opts_lock, flags);
flush_work(&opts->work);
spin_lock_irqsave(&opts_lock, flags);
if (opts->device_id >= 0)
ida_free(&android_ida, opts->device_id);
android_opts = NULL;
ida_destroy(&opts->function_ida);
device_destroy(dev->class, dev->devt);
spin_unlock_irqrestore(&opts_lock, flags);
}
EXPORT_SYMBOL_GPL(android_device_destroy);
void __android_set_connected(struct android_uevent_opts *opts,
bool connected)
{
unsigned long flags;
spin_lock_irqsave(&opts_lock, flags);
// Don't send the uevent if connected state is not changed
if (opts->connected != connected) {
opts->connected = connected;
schedule_work(&opts->work);
}
spin_unlock_irqrestore(&opts_lock, flags);
}
void __android_set_configured(struct android_uevent_opts *opts,
bool configured)
{
unsigned long flags;
spin_lock_irqsave(&opts_lock, flags);
// Don't send the uevent if configure state is not changed
if (opts->configured != configured) {
opts->configured = configured;
schedule_work(&opts->work);
}
spin_unlock_irqrestore(&opts_lock, flags);
}
void android_set_connected(struct android_uevent_opts *opts)
{
__android_set_connected(opts, true);
}
EXPORT_SYMBOL_GPL(android_set_connected);
void android_set_disconnected(struct android_uevent_opts *opts)
{
__android_set_connected(opts, false);
}
EXPORT_SYMBOL_GPL(android_set_disconnected);
void android_set_configured(struct android_uevent_opts *opts)
{
__android_set_configured(opts, true);
}
EXPORT_SYMBOL_GPL(android_set_configured);
void android_set_unconfigured(struct android_uevent_opts *opts)
{
__android_set_configured(opts, false);
}
EXPORT_SYMBOL_GPL(android_set_unconfigured);
struct device *android_create_function_device(char *name, void *drvdata,
const struct attribute_group **groups)
{
struct android_uevent_opts *opts;
struct device *dev;
unsigned long flags;
int id;
spin_lock_irqsave(&opts_lock, flags);
opts = android_opts;
if (IS_ERR_OR_NULL(opts) || IS_ERR_OR_NULL(opts->dev)) {
spin_unlock_irqrestore(&opts_lock, flags);
return ERR_PTR(-ENODEV);
}
id = ida_alloc(&opts->function_ida, GFP_ATOMIC);
if (id < 0) {
spin_unlock_irqrestore(&opts_lock, flags);
return ERR_PTR(id);
}
// device_create_with_groups can sleep, so we must unlock first
spin_unlock_irqrestore(&opts_lock, flags);
dev = device_create_with_groups(&android_usb_class, opts->dev,
MKDEV(0, id), drvdata, groups, name);
return dev;
}
EXPORT_SYMBOL_GPL(android_create_function_device);
void android_remove_function_device(struct device *dev)
{
struct android_uevent_opts *opts;
unsigned long flags;
device_destroy(&android_usb_class, dev->devt);
spin_lock_irqsave(&opts_lock, flags);
opts = android_opts;
if (IS_ERR_OR_NULL(opts)) {
spin_unlock_irqrestore(&opts_lock, flags);
return;
}
ida_free(&opts->function_ida, MINOR(dev->devt));
spin_unlock_irqrestore(&opts_lock, flags);
}
EXPORT_SYMBOL_GPL(android_remove_function_device);
@@ -0,0 +1,150 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2024 Google LLC
*/
#ifndef __ANDROID_CONFIGFS_UEVENT_H
#define __ANDROID_CONFIGFS_UEVENT_H
#ifdef CONFIG_ANDROID_USB_CONFIGFS_UEVENT
#include <linux/usb/android_configfs_uevent.h>
/**
* android_class_create - essentially the __init() function for the
* configfs_uevent library, since it is not a standalone driver.
*
* Creates the android_usb class of device
*
* Returns: the result of class_register (0 for success, err otherwise)
*/
int android_class_create(void);
/**
* android_class_destroy - essentially the __exit() function for the
* configfs_uevent library, since it is not a standalone driver.
*
* Removes the android_usb class of devices and performs any necessary
* cleanup.
*/
void android_class_destroy(void);
/**
* android_device_create - Creates an android device instance and
* a state attribute file which can be read to determine the state of the
* usb gadget.
* @opts: contextual data for the configfs_uevent library.
*
* Note: the state file created by this function mimics the functionaltiy
* of the UDC driver and is likely redundant, but maintained for legacy
* support.
*
* The state can be one of "DISCONNECTED", "CONNECTED", or "CONFIGURED"
*
* Returns: 0 for success, or if an error is encountered during ida_allocation
* or device_creation, that error is returned.
*/
int android_device_create(struct android_uevent_opts *opts);
/**
* android_device_destroy - Removes the android device instance and performs
* any necessary cleanup.
* @opts: contextual data for the configfs_uevent library.
*/
void android_device_destroy(struct android_uevent_opts *opts);
/**
* android_set_connected - set the internal state of android_uevent_opts to
* connected and schedule the work to emit a uevent with this status update.
* @opts: contextual data for the configfs_uevent library
*
* This should be called by the gadget composite driver when a usb_ctrlrequest
* is received by the gadget driver.
*
* This function locks the android specific android_uevent_opts->lock and
* therefore should not require locking the containing composite device
* structure as the internal lock is also used in the teardown path of the
* composite driver in android_device_destroy().
*/
void android_set_connected(struct android_uevent_opts *opts);
/**
* android_set_disconnected - reset the internal state of android_uevent_opts to
* disconnected and schedule the work to emit a uevent with this status update.
* @opts: contextual data for the configfs_uevent library
*
* This should be called by the gadget composite driver when the link is
* disconnected.
*
* This function locks the android specific android_uevent_opts->lock and
* therefore should not require locking the containing composite device
* structure as the internal lock is also used in the teardown path of the
* composite driver in android_device_destroy().
*/
void android_set_disconnected(struct android_uevent_opts *opts);
/**
* android_set_configured - set the internal state of android_uevent_opts to
* configured and schedule the work to emit a uevent with this status update.
* @opts: contextual data for the configfs_uevent library
*
* This should be called by the gadget composite driver when the configuration
* is applied to the gadget composite device
*
* This function locks the android specific android_uevent_opts->lock and
* therefore should not require locking the containing composite device
* structure as the internal lock is also used in the teardown path of the
* composite driver in android_device_destroy().
*/
void android_set_configured(struct android_uevent_opts *opts);
/**
* android_set_unconfigured - reset the internal state of android_uevent_opts to
* unconfigured and schedule the work to emit a uevent with this status update.
* @opts: contextual data for the configfs_uevent library
*
* This should be called by the gadget composite driver when the gadget
* configuration is torn down.
*
* This function locks the android specific android_uevent_opts->lock and
* therefore should not require locking the containing composite device
* structure as the internal lock is also used in the teardown path of the
* composite driver in android_device_destroy().
*/
void android_set_unconfigured(struct android_uevent_opts *opts);
#else
static inline int android_class_create(void)
{
return 0;
}
static inline void android_class_destroy(void)
{
}
static inline int android_device_create(struct android_uevent_opts *opts)
{
return 0;
}
static inline void android_device_destroy(struct android_uevent_opts *opts)
{
}
static inline void android_set_connected(struct android_uevent_opts *opts)
{
}
static inline void android_set_disconnected(struct android_uevent_opts *opts)
{
}
static inline void android_set_configured(struct android_uevent_opts *opts)
{
}
static inline void android_set_unconfigured(struct android_uevent_opts *opts)
{
}
#endif /* CONFIG_ANDROID_USB_CONFIGFS_UEVENT */
#endif /* __ANDROID_CONFIGFS_UEVENT_H */
+7
View File
@@ -22,6 +22,7 @@
#include <asm/unaligned.h>
#include "u_os_desc.h"
#include "android_configfs_uevent.h"
/**
* struct usb_os_string - represents OS String to be reported by a gadget
@@ -941,6 +942,7 @@ static void reset_config(struct usb_composite_dev *cdev)
bitmap_zero(f->endpoints, 32);
}
cdev->config = NULL;
android_set_unconfigured(&cdev->android_opts);
cdev->delayed_status = 0;
}
@@ -1788,6 +1790,8 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
struct usb_function *iter;
u8 endp;
android_set_connected(&cdev->android_opts);
if (w_length > USB_COMP_EP0_BUFSIZ) {
if (ctrl->bRequestType & USB_DIR_IN) {
/* Cast away the const, we are going to overwrite on purpose. */
@@ -1922,6 +1926,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
spin_lock(&cdev->lock);
value = set_config(cdev, ctrl, w_value);
spin_unlock(&cdev->lock);
android_set_configured(&cdev->android_opts);
break;
case USB_REQ_GET_CONFIGURATION:
if (ctrl->bRequestType != USB_DIR_IN)
@@ -2299,6 +2304,8 @@ static void __composite_disconnect(struct usb_gadget *gadget)
struct usb_composite_dev *cdev = get_gadget_data(gadget);
unsigned long flags;
android_set_disconnected(&cdev->android_opts);
/* REVISIT: should we have config and device level
* disconnect callbacks?
*/
+23 -1
View File
@@ -9,6 +9,7 @@
#include <linux/usb/gadget_configfs.h>
#include <linux/usb/webusb.h>
#include "configfs.h"
#include "android_configfs_uevent.h"
#include "u_f.h"
#include "u_os_desc.h"
@@ -283,7 +284,11 @@ static ssize_t gadget_dev_desc_UDC_store(struct config_item *item,
mutex_lock(&gi->lock);
if (!strlen(name)) {
/*
* ANDROID: Not exactly sure why we need this "none", but worried it
* would break something if removed.
*/
if (!strlen(name) || strcmp(name, "none") == 0) {
ret = unregister_gadget(gi);
if (ret)
goto err;
@@ -2034,10 +2039,16 @@ static struct config_group *gadgets_make(
if (!gi->composite.gadget_driver.function)
goto out_free_driver_name;
if (android_device_create(&gi->cdev.android_opts))
goto out_free_driver_name_and_function;
return &gi->group;
out_free_driver_name:
kfree(gi->composite.gadget_driver.driver.name);
out_free_driver_name_and_function:
kfree(gi->composite.gadget_driver.driver.name);
kfree(gi->composite.gadget_driver.function);
err:
kfree(gi);
return ERR_PTR(-ENOMEM);
@@ -2045,6 +2056,10 @@ err:
static void gadgets_drop(struct config_group *group, struct config_item *item)
{
struct gadget_info *gi;
gi = container_of(to_config_group(item), struct gadget_info, group);
android_device_destroy(&gi->cdev.android_opts);
config_item_put(item);
}
@@ -2084,7 +2099,13 @@ static int __init gadget_cfs_init(void)
config_group_init(&gadget_subsys.su_group);
ret = android_class_create();
if (ret)
return ret;
ret = configfs_register_subsystem(&gadget_subsys);
if (ret)
android_class_destroy();
return ret;
}
module_init(gadget_cfs_init);
@@ -2092,5 +2113,6 @@ module_init(gadget_cfs_init);
static void __exit gadget_cfs_exit(void)
{
configfs_unregister_subsystem(&gadget_subsys);
android_class_destroy();
}
module_exit(gadget_cfs_exit);
@@ -0,0 +1,58 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2024 Google LLC
*/
#ifndef _ANDROID_USB_CONFIGFS_UEVENT_H
#define _ANDROID_USB_CONFIGFS_UEVENT_H
#ifdef CONFIG_ANDROID_USB_CONFIGFS_UEVENT
#include <linux/device.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
struct android_uevent_opts {
struct device *dev;
int device_id;
bool connected;
bool configured;
bool sw_connected;
struct work_struct work;
struct ida function_ida;
};
/**
* android_create_function_device - creates a device within the android_usb
* class with a new minor number.
* @name: the name for the device which is to be created
* @drvdata: the data to be added to the device for callbacks, can be NULL
* @groups: NULL-terminated list of attribute groups to be created, can be NULL
*
* This should be called by function drivers which wish to register a device
* within the android_usb class.
*
* Returns: a pointer to the newly created device upon success, or an ERR_PTR
* for the encountered error.
*/
struct device *android_create_function_device(char *name, void *drvdata,
const struct attribute_group **groups);
/**
* android_remove_function_device - destroys a device which was created by
* calling android_create_function_device, and performs any necessary cleanup.
* @dev: the device to be destroyed
*/
void android_remove_function_device(struct device *dev);
#else
struct android_uevent_opts {};
static inline struct device *android_create_function_device(char *name)
{
return ERR_PTR(-ENODEV);
}
static inline void android_remove_function_device(struct device *dev)
{
}
#endif /* CONFIG_ANDROID_USB_CONFIGFS_UEVENT */
#endif /* _ANDROID_USB_CONFIGFS_UEVENT_H */
+3
View File
@@ -28,6 +28,7 @@
#include <linux/usb/webusb.h>
#include <linux/log2.h>
#include <linux/configfs.h>
#include <linux/usb/android_configfs_uevent.h>
/*
* USB function drivers should return USB_GADGET_DELAYED_STATUS if they
@@ -502,6 +503,8 @@ struct usb_composite_dev {
/* protects deactivations and delayed_status counts*/
spinlock_t lock;
struct android_uevent_opts android_opts;
/* public: */
unsigned int setup_pending:1;
unsigned int os_desc_pending:1;