* [PATCH RFC 00/14] usb: gadget: f_uac: Add support for alt mode settings
@ 2024-09-28 15:08 crwulff
2024-09-28 15:08 ` [PATCH RFC 01/14] usb: gadget: f_uac: Refactor common configfs attribute defines used in UAC1/2 crwulff
` (13 more replies)
0 siblings, 14 replies; 15+ messages in thread
From: crwulff @ 2024-09-28 15:08 UTC (permalink / raw)
To: linux-usb
Cc: Pavel Hofman, Greg Kroah-Hartman, James Gruber, Jeff Johnson,
John Keeping, Jonathan Corbet, Lee Jones, Perr Zhang, linux-doc,
linux-kernel
This patch series adds support for additional alternate settings for
UAC1/2 gadgets. See references [2] and [3] for discussions on the API.
*** Userspace API ***
* Add the ability to create c_alt.x and p_alt.x directories
under the function with settings that are available per alternate mode.
The existing API is preserved and used as defaults when creating c/p_alt.x.
Subsequent changes to the function-wide settings don't change existing
c/p_alt.x directories.
If no directories for c/p_alt.1 exist, alternate setting 1 will continue
to be created from the function-wide settings.
Settings that can be changed per-alt-mode (bAlternateSetting):
- name (iInterface - Interface Descriptor name)
- chmask (wChannelConfig - Channel mask)
- ssize (bSubframeSize - Sample size bytes)
- terminal_type (wTerminalType - Terminal Type)
- sync (OUT only) (bmAttributes - Endpoint Synch Type)
- hs_bint (bInterval - Used for HS and above, 0=auto)
- Terminal names (see below)
* Add the ability to set terminal name strings. Defaults are the same as
the hard-coded strings that were present in UAC1/2 functions. These are
now present as p_/c_ function-wide variants and in p/c_alt.x directories.
(Note: UAC1 defaults aren't the same as UAC2)
These are now settable with:
- it_name (iTerminal - Input Terminal name)
- it_ch_name (iChannelNames - Input Terminal first channel name)
- fu_vol_name (iFeature - Feature Unit, for mute/volume, name)
- ot_name (iTerminal - Output Terminal name)
* UAC1 specific UAPI changes (to bring it up to parity with UAC2)
- Added support for c_sync (support for USB Out ASYNC mode)
- Added support for p/c_terminal_type (note that these DO correct the
order from UAC2 so they follow the device centric view, and align with
the other c/p options)
- Added p/c_hs_bint
*** Descriptors ***
* Strings are now dynamically allocated as needed. For simplicity the
algorithm is O(n^2) and allocates the maximum table size (256) (2KB on
64-bit systems.) This allows multiple path descriptors to reuse the same
string ids if they are all named the same.
* Path descriptors (Input Terminal -> <Feature Unit> -> Output Terminal)
are now created based on differences in settings. Any differences in
names or chmask will create a new set of terminals.
* Descriptors for each alternate setting are now attached to those settings
and many global descriptors are removed. Maximum size for each alternate
setting is calculated for its enpoints. All alternate settings use the
same set of endpoint numbers though.
* Differences from previously created descriptors with the default settings
- String IDs are allocated differently. Referenced strings all match but
have different numbers.
- Unit/Terminal IDs are allocated differently. Source IDs/Associated
Terminal numbers are different, but reference the same terminals.
- UAC2 Terminal descriptors are not quite in the same order.
Old order was IT, FU, IT, FU, OT, OT. Now it is IT, FU, OT, IT, FU, OT.
This change in order doesn't really matter, but will show up if you
look at a diff between the two.
- UAC1 Endpoint wMaxPacketSize was hard-coded to 200. Calcluated value
is now 196 for default settings.
* UAC1 Specific Bug Fixes
- LowSpeed interval is fixed (bInterval was 4 for all speeds)
- SuperSpeed descriptors are fixed. The old implementation had only
partial support for SuperSpeed and creates invalid descriptors in
the default case. (Turning off volume/mute control is the only
working case for SuperSpeed.)
*** Back-end Functionality ***
* Volume Mute Controls
- When using a combination of alt modes that requires separate terminals
the volume/mute functional unit must also be duplicated. This causes
there to be multiple volume controls on the USB side. In this case,
there is still only one volume/mute control on the ALSA side and
all associated instances on the USB side are synchronized. (Changing
any one of the associated volume/mute controls causes the others to
be updated to the same value.)
*** Remaining Work ***
* Alternate settings aren't yet updating the u_audio back end so
the function-wide settings are all that is used by the ALSA interface.
This effectively means that changing any settings that affect the
audio samples (ssize/chmask) won't yet pass good audio.
* Testing. Defaults and a few random combination have been tested with
the Dummy HCD driver (with patch [1] to allow isochronous endpoints) but
no audio testing has been done yet (just UAPI and descriptor testing.)
These are both pending my having a target that has an up-to-date kernel
for some real hardware testing. This is in-progress but may take a bit
to get working as my current device is running 4.19.168.
*** Deficiencies (not planned to fix) ***
* There appears to be no way to prevent removal of c_alt.x/p_alt.x
while the function is bound. (This is not unique to these folders,
as other folders may be removed after a function is bound and UDC set.)
This doesn't appear to cause any critical failures, but removing
c_alt.x/p_alt.x while the gadget is active will cause those alt modes
to no longer function correctly.
*** References ***
[1] https://lore.kernel.org/linux-usb/20220126132249.2133168-1-m.grzeschik@pengutronix.de/
Patch for partial ISOC support in dummy_hcd. As the patch mentions,
this is only useful for validating non-ISOC parts of gadgets, but
does allow them to bind for testing of descriptors at least.
[2] https://lore.kernel.org/linux-usb/CO1PR17MB54195BE778868AFDFE2DCB36E1112@CO1PR17MB5419.namprd17.prod.outlook.com/
Discussion thread on UAPI for adding strings
[3] https://lore.kernel.org/linux-usb/35be4668-58d3-894a-72cf-de1afaacae45@ivitera.com/
Discussion thread on UAPI for adding alt modes
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH RFC 01/14] usb: gadget: f_uac: Refactor common configfs attribute defines used in UAC1/2.
2024-09-28 15:08 [PATCH RFC 00/14] usb: gadget: f_uac: Add support for alt mode settings crwulff
@ 2024-09-28 15:08 ` crwulff
2024-09-28 15:08 ` [PATCH RFC 02/14] usb: gadget: f_uac1: Fix fs/hs/ss descriptors to have correct values crwulff
` (12 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: crwulff @ 2024-09-28 15:08 UTC (permalink / raw)
To: linux-usb
Cc: Pavel Hofman, Greg Kroah-Hartman, James Gruber, Jeff Johnson,
John Keeping, Jonathan Corbet, Lee Jones, Perr Zhang, linux-doc,
linux-kernel, Chris Wulff
From: Chris Wulff <crwulff@gmail.com>
Consolidate a bunch of common #defines used by both f_uac1 and f_uac2
to make it easier to maintain. Only the #defines moved and the same static
functions get created in each module as before. Formatters are also #define
which makes slightly smaller code.
Signed-off-by: Chris Wulff <crwulff@gmail.com>
---
drivers/usb/gadget/function/f_uac1.c | 148 +--------------
drivers/usb/gadget/function/f_uac2.c | 207 +--------------------
drivers/usb/gadget/function/u_uac_utils.h | 217 ++++++++++++++++++++++
3 files changed, 235 insertions(+), 337 deletions(-)
create mode 100644 drivers/usb/gadget/function/u_uac_utils.h
diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c
index c87e74afc881..eeedcfa61fa1 100644
--- a/drivers/usb/gadget/function/f_uac1.c
+++ b/drivers/usb/gadget/function/f_uac1.c
@@ -19,6 +19,7 @@
#include "u_audio.h"
#include "u_uac1.h"
+#include "u_uac_utils.h"
/* UAC1 spec: 3.7.2.3 Audio Channel Cluster Format */
#define UAC1_CHANNEL_MASK 0x0FFF
@@ -1516,151 +1517,18 @@ static struct configfs_item_operations f_uac1_item_ops = {
.release = f_uac1_attr_release,
};
-#define uac1_kstrtou32 kstrtou32
-#define uac1_kstrtos16 kstrtos16
-#define uac1_kstrtobool(s, base, res) kstrtobool((s), (res))
-
-static const char *u32_fmt = "%u\n";
-static const char *s16_fmt = "%hd\n";
-static const char *bool_fmt = "%u\n";
-
+#define UAC1_ATTR_TO_OPTS struct f_uac1_opts *opts = to_f_uac1_opts(item)
#define UAC1_ATTRIBUTE(type, name) \
-static ssize_t f_uac1_opts_##name##_show( \
- struct config_item *item, \
- char *page) \
-{ \
- struct f_uac1_opts *opts = to_f_uac1_opts(item); \
- int result; \
- \
- mutex_lock(&opts->lock); \
- result = sprintf(page, type##_fmt, opts->name); \
- mutex_unlock(&opts->lock); \
- \
- return result; \
-} \
- \
-static ssize_t f_uac1_opts_##name##_store( \
- struct config_item *item, \
- const char *page, size_t len) \
-{ \
- struct f_uac1_opts *opts = to_f_uac1_opts(item); \
- int ret; \
- type num; \
- \
- mutex_lock(&opts->lock); \
- if (opts->refcnt) { \
- ret = -EBUSY; \
- goto end; \
- } \
- \
- ret = uac1_kstrto##type(page, 0, &num); \
- if (ret) \
- goto end; \
- \
- opts->name = num; \
- ret = len; \
- \
-end: \
- mutex_unlock(&opts->lock); \
- return ret; \
-} \
- \
-CONFIGFS_ATTR(f_uac1_opts_, name)
+ UAC_ATTRIBUTE(f_uac1_opts, UAC1_ATTR_TO_OPTS, opts, \
+ opts->lock, opts->refcnt, type, name)
#define UAC1_RATE_ATTRIBUTE(name) \
-static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \
- char *page) \
-{ \
- struct f_uac1_opts *opts = to_f_uac1_opts(item); \
- int result = 0; \
- int i; \
- \
- mutex_lock(&opts->lock); \
- page[0] = '\0'; \
- for (i = 0; i < UAC_MAX_RATES; i++) { \
- if (opts->name##s[i] == 0) \
- break; \
- result += sprintf(page + strlen(page), "%u,", \
- opts->name##s[i]); \
- } \
- if (strlen(page) > 0) \
- page[strlen(page) - 1] = '\n'; \
- mutex_unlock(&opts->lock); \
- \
- return result; \
-} \
- \
-static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
- const char *page, size_t len) \
-{ \
- struct f_uac1_opts *opts = to_f_uac1_opts(item); \
- char *split_page = NULL; \
- int ret = -EINVAL; \
- char *token; \
- u32 num; \
- int i; \
- \
- mutex_lock(&opts->lock); \
- if (opts->refcnt) { \
- ret = -EBUSY; \
- goto end; \
- } \
- \
- i = 0; \
- memset(opts->name##s, 0x00, sizeof(opts->name##s)); \
- split_page = kstrdup(page, GFP_KERNEL); \
- while ((token = strsep(&split_page, ",")) != NULL) { \
- ret = kstrtou32(token, 0, &num); \
- if (ret) \
- goto end; \
- \
- opts->name##s[i++] = num; \
- ret = len; \
- }; \
- \
-end: \
- kfree(split_page); \
- mutex_unlock(&opts->lock); \
- return ret; \
-} \
- \
-CONFIGFS_ATTR(f_uac1_opts_, name)
+ UAC_RATE_ATTRIBUTE(f_uac1_opts, UAC1_ATTR_TO_OPTS, opts, \
+ opts->lock, opts->refcnt, name)
#define UAC1_ATTRIBUTE_STRING(name) \
-static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \
- char *page) \
-{ \
- struct f_uac1_opts *opts = to_f_uac1_opts(item); \
- int result; \
- \
- mutex_lock(&opts->lock); \
- result = scnprintf(page, sizeof(opts->name), "%s", opts->name); \
- mutex_unlock(&opts->lock); \
- \
- return result; \
-} \
- \
-static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
- const char *page, size_t len) \
-{ \
- struct f_uac1_opts *opts = to_f_uac1_opts(item); \
- int ret = 0; \
- \
- mutex_lock(&opts->lock); \
- if (opts->refcnt) { \
- ret = -EBUSY; \
- goto end; \
- } \
- \
- ret = scnprintf(opts->name, min(sizeof(opts->name), len), \
- "%s", page); \
- \
-end: \
- mutex_unlock(&opts->lock); \
- return ret; \
-} \
- \
-CONFIGFS_ATTR(f_uac1_opts_, name)
+ UAC_ATTRIBUTE_STRING(f_uac1_opts, UAC1_ATTR_TO_OPTS, opts, \
+ opts->lock, opts->refcnt, name)
UAC1_ATTRIBUTE(u32, c_chmask);
UAC1_RATE_ATTRIBUTE(c_srate);
diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index 1cdda44455b3..2142a0951f71 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -17,6 +17,7 @@
#include "u_audio.h"
#include "u_uac2.h"
+#include "u_uac_utils.h"
/* UAC2 spec: 4.1 Audio Channel Cluster Descriptor */
#define UAC2_CHANNEL_MASK 0x07FFFFFF
@@ -1877,210 +1878,22 @@ static struct configfs_item_operations f_uac2_item_ops = {
.release = f_uac2_attr_release,
};
-#define uac2_kstrtou8 kstrtou8
-#define uac2_kstrtou32 kstrtou32
-#define uac2_kstrtos16 kstrtos16
-#define uac2_kstrtobool(s, base, res) kstrtobool((s), (res))
-
-static const char *u8_fmt = "%u\n";
-static const char *u32_fmt = "%u\n";
-static const char *s16_fmt = "%hd\n";
-static const char *bool_fmt = "%u\n";
-
+#define UAC2_ATTR_TO_OPTS struct f_uac2_opts *opts = to_f_uac2_opts(item)
#define UAC2_ATTRIBUTE(type, name) \
-static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \
- char *page) \
-{ \
- struct f_uac2_opts *opts = to_f_uac2_opts(item); \
- int result; \
- \
- mutex_lock(&opts->lock); \
- result = sprintf(page, type##_fmt, opts->name); \
- mutex_unlock(&opts->lock); \
- \
- return result; \
-} \
- \
-static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \
- const char *page, size_t len) \
-{ \
- struct f_uac2_opts *opts = to_f_uac2_opts(item); \
- int ret; \
- type num; \
- \
- mutex_lock(&opts->lock); \
- if (opts->refcnt) { \
- ret = -EBUSY; \
- goto end; \
- } \
- \
- ret = uac2_kstrto##type(page, 0, &num); \
- if (ret) \
- goto end; \
- \
- opts->name = num; \
- ret = len; \
- \
-end: \
- mutex_unlock(&opts->lock); \
- return ret; \
-} \
- \
-CONFIGFS_ATTR(f_uac2_opts_, name)
+ UAC_ATTRIBUTE(f_uac2_opts, UAC2_ATTR_TO_OPTS, opts, \
+ opts->lock, opts->refcnt, type, name)
#define UAC2_ATTRIBUTE_SYNC(name) \
-static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \
- char *page) \
-{ \
- struct f_uac2_opts *opts = to_f_uac2_opts(item); \
- int result; \
- char *str; \
- \
- mutex_lock(&opts->lock); \
- switch (opts->name) { \
- case USB_ENDPOINT_SYNC_ASYNC: \
- str = "async"; \
- break; \
- case USB_ENDPOINT_SYNC_ADAPTIVE: \
- str = "adaptive"; \
- break; \
- default: \
- str = "unknown"; \
- break; \
- } \
- result = sprintf(page, "%s\n", str); \
- mutex_unlock(&opts->lock); \
- \
- return result; \
-} \
- \
-static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \
- const char *page, size_t len) \
-{ \
- struct f_uac2_opts *opts = to_f_uac2_opts(item); \
- int ret = 0; \
- \
- mutex_lock(&opts->lock); \
- if (opts->refcnt) { \
- ret = -EBUSY; \
- goto end; \
- } \
- \
- if (!strncmp(page, "async", 5)) \
- opts->name = USB_ENDPOINT_SYNC_ASYNC; \
- else if (!strncmp(page, "adaptive", 8)) \
- opts->name = USB_ENDPOINT_SYNC_ADAPTIVE; \
- else { \
- ret = -EINVAL; \
- goto end; \
- } \
- \
- ret = len; \
- \
-end: \
- mutex_unlock(&opts->lock); \
- return ret; \
-} \
- \
-CONFIGFS_ATTR(f_uac2_opts_, name)
+ UAC_ATTRIBUTE_SYNC(f_uac2_opts, UAC2_ATTR_TO_OPTS, opts, \
+ opts->lock, opts->refcnt, name)
#define UAC2_RATE_ATTRIBUTE(name) \
-static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \
- char *page) \
-{ \
- struct f_uac2_opts *opts = to_f_uac2_opts(item); \
- int result = 0; \
- int i; \
- \
- mutex_lock(&opts->lock); \
- page[0] = '\0'; \
- for (i = 0; i < UAC_MAX_RATES; i++) { \
- if (opts->name##s[i] == 0) \
- break; \
- result += sprintf(page + strlen(page), "%u,", \
- opts->name##s[i]); \
- } \
- if (strlen(page) > 0) \
- page[strlen(page) - 1] = '\n'; \
- mutex_unlock(&opts->lock); \
- \
- return result; \
-} \
- \
-static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \
- const char *page, size_t len) \
-{ \
- struct f_uac2_opts *opts = to_f_uac2_opts(item); \
- char *split_page = NULL; \
- int ret = -EINVAL; \
- char *token; \
- u32 num; \
- int i; \
- \
- mutex_lock(&opts->lock); \
- if (opts->refcnt) { \
- ret = -EBUSY; \
- goto end; \
- } \
- \
- i = 0; \
- memset(opts->name##s, 0x00, sizeof(opts->name##s)); \
- split_page = kstrdup(page, GFP_KERNEL); \
- while ((token = strsep(&split_page, ",")) != NULL) { \
- ret = kstrtou32(token, 0, &num); \
- if (ret) \
- goto end; \
- \
- opts->name##s[i++] = num; \
- ret = len; \
- }; \
- \
-end: \
- kfree(split_page); \
- mutex_unlock(&opts->lock); \
- return ret; \
-} \
- \
-CONFIGFS_ATTR(f_uac2_opts_, name)
+ UAC_RATE_ATTRIBUTE(f_uac2_opts, UAC2_ATTR_TO_OPTS, opts, \
+ opts->lock, opts->refcnt, name)
#define UAC2_ATTRIBUTE_STRING(name) \
-static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \
- char *page) \
-{ \
- struct f_uac2_opts *opts = to_f_uac2_opts(item); \
- int result; \
- \
- mutex_lock(&opts->lock); \
- result = scnprintf(page, sizeof(opts->name), "%s", opts->name); \
- mutex_unlock(&opts->lock); \
- \
- return result; \
-} \
- \
-static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \
- const char *page, size_t len) \
-{ \
- struct f_uac2_opts *opts = to_f_uac2_opts(item); \
- int ret = 0; \
- \
- mutex_lock(&opts->lock); \
- if (opts->refcnt) { \
- ret = -EBUSY; \
- goto end; \
- } \
- \
- if (len && page[len - 1] == '\n') \
- len--; \
- \
- ret = scnprintf(opts->name, min(sizeof(opts->name), len + 1), \
- "%s", page); \
- \
-end: \
- mutex_unlock(&opts->lock); \
- return ret; \
-} \
- \
-CONFIGFS_ATTR(f_uac2_opts_, name)
+ UAC_ATTRIBUTE_STRING(f_uac2_opts, UAC2_ATTR_TO_OPTS, opts, \
+ opts->lock, opts->refcnt, name)
UAC2_ATTRIBUTE(u32, p_chmask);
UAC2_RATE_ATTRIBUTE(p_srate);
diff --git a/drivers/usb/gadget/function/u_uac_utils.h b/drivers/usb/gadget/function/u_uac_utils.h
new file mode 100644
index 000000000000..5f88e31103dd
--- /dev/null
+++ b/drivers/usb/gadget/function/u_uac_utils.h
@@ -0,0 +1,217 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * u_uac_utils.h -- Utilities for UAC1/2 function driver
+ *
+ * Copyright (C) 2024
+ * Author: Chris Wulff <crwulff@gmail.com>
+ */
+
+#ifndef __U_UAC_UTILS_H
+#define __U_UAC_UTILS_H
+
+#define uac_kstrtou8 kstrtou8
+#define uac_kstrtos16 kstrtos16
+#define uac_kstrtou32 kstrtou32
+#define uac_kstrtobool(s, base, res) kstrtobool((s), (res))
+
+#define u8_FMT "%u\n"
+#define u32_FMT "%u\n"
+#define s16_FMT "%hd\n"
+#define bool_FMT "%u\n"
+
+#define UAC_ATTRIBUTE(prefix, to_struct, var, lock, refcnt, type, name) \
+static ssize_t prefix##_##name##_show(struct config_item *item, \
+ char *page) \
+{ \
+ to_struct; \
+ int result; \
+ \
+ mutex_lock(&lock); \
+ result = sprintf(page, type##_FMT, var->name); \
+ mutex_unlock(&lock); \
+ \
+ return result; \
+} \
+ \
+static ssize_t prefix##_##name##_store(struct config_item *item, \
+ const char *page, size_t len) \
+{ \
+ to_struct; \
+ int ret; \
+ type num; \
+ \
+ mutex_lock(&lock); \
+ if (refcnt) { \
+ ret = -EBUSY; \
+ goto end; \
+ } \
+ \
+ ret = uac_kstrto##type(page, 0, &num); \
+ if (ret) \
+ goto end; \
+ \
+ var->name = num; \
+ ret = len; \
+ \
+end: \
+ mutex_unlock(&lock); \
+ return ret; \
+} \
+ \
+CONFIGFS_ATTR(prefix##_, name)
+
+#define UAC_RATE_ATTRIBUTE(prefix, to_struct, var, lock, refcnt, name) \
+static ssize_t prefix##_##name##_show(struct config_item *item, \
+ char *page) \
+{ \
+ to_struct; \
+ int result = 0; \
+ int i; \
+ \
+ mutex_lock(&lock); \
+ page[0] = '\0'; \
+ for (i = 0; i < UAC_MAX_RATES; i++) { \
+ if (var->name##s[i] == 0) \
+ break; \
+ result += sprintf(page + strlen(page), "%u,", \
+ var->name##s[i]); \
+ } \
+ if (strlen(page) > 0) \
+ page[strlen(page) - 1] = '\n'; \
+ mutex_unlock(&lock); \
+ \
+ return result; \
+} \
+ \
+static ssize_t prefix##_##name##_store(struct config_item *item, \
+ const char *page, size_t len) \
+{ \
+ to_struct; \
+ char *split_page = NULL; \
+ int ret = -EINVAL; \
+ char *token; \
+ u32 num; \
+ int i; \
+ \
+ mutex_lock(&lock); \
+ if (refcnt) { \
+ ret = -EBUSY; \
+ goto end; \
+ } \
+ \
+ i = 0; \
+ memset(var->name##s, 0x00, sizeof(var->name##s)); \
+ split_page = kstrdup(page, GFP_KERNEL); \
+ while ((token = strsep(&split_page, ",")) != NULL) { \
+ ret = kstrtou32(token, 0, &num); \
+ if (ret) \
+ goto end; \
+ \
+ var->name##s[i++] = num; \
+ ret = len; \
+ }; \
+ \
+end: \
+ kfree(split_page); \
+ mutex_unlock(&lock); \
+ return ret; \
+} \
+ \
+CONFIGFS_ATTR(prefix##_, name)
+
+#define UAC_ATTRIBUTE_STRING(prefix, to_struct, var, lock, refcnt, name) \
+static ssize_t prefix##_##name##_show(struct config_item *item, \
+ char *page) \
+{ \
+ to_struct; \
+ int result; \
+ \
+ mutex_lock(&lock); \
+ result = scnprintf(page, sizeof(var->name), "%s", var->name); \
+ mutex_unlock(&lock); \
+ \
+ return result; \
+} \
+ \
+static ssize_t prefix##_##name##_store(struct config_item *item, \
+ const char *page, size_t len) \
+{ \
+ to_struct; \
+ int ret = 0; \
+ \
+ mutex_lock(&lock); \
+ if (refcnt) { \
+ ret = -EBUSY; \
+ goto end; \
+ } \
+ \
+ if (len && page[len - 1] == '\n') \
+ len--; \
+ \
+ ret = scnprintf(var->name, min(sizeof(var->name), len + 1), \
+ "%s", page); \
+ \
+end: \
+ mutex_unlock(&lock); \
+ return ret; \
+} \
+ \
+CONFIGFS_ATTR(prefix##_, name)
+
+#define UAC_ATTRIBUTE_SYNC(prefix, to_struct, var, lock, refcnt, name) \
+static ssize_t prefix##_##name##_show(struct config_item *item, \
+ char *page) \
+{ \
+ to_struct; \
+ int result; \
+ char *str; \
+ \
+ mutex_lock(&lock); \
+ switch (var->name) { \
+ case USB_ENDPOINT_SYNC_ASYNC: \
+ str = "async"; \
+ break; \
+ case USB_ENDPOINT_SYNC_ADAPTIVE: \
+ str = "adaptive"; \
+ break; \
+ default: \
+ str = "unknown"; \
+ break; \
+ } \
+ result = sprintf(page, "%s\n", str); \
+ mutex_unlock(&lock); \
+ \
+ return result; \
+} \
+ \
+static ssize_t prefix##_##name##_store(struct config_item *item, \
+ const char *page, size_t len) \
+{ \
+ to_struct; \
+ int ret = 0; \
+ \
+ mutex_lock(&lock); \
+ if (refcnt) { \
+ ret = -EBUSY; \
+ goto end; \
+ } \
+ \
+ if (!strncmp(page, "async", 5)) \
+ var->name = USB_ENDPOINT_SYNC_ASYNC; \
+ else if (!strncmp(page, "adaptive", 8)) \
+ var->name = USB_ENDPOINT_SYNC_ADAPTIVE; \
+ else { \
+ ret = -EINVAL; \
+ goto end; \
+ } \
+ \
+ ret = len; \
+ \
+end: \
+ mutex_unlock(&lock); \
+ return ret; \
+} \
+ \
+CONFIGFS_ATTR(prefix##_, name)
+
+#endif /* __U_UAC_UTILS_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH RFC 02/14] usb: gadget: f_uac1: Fix fs/hs/ss descriptors to have correct values
2024-09-28 15:08 [PATCH RFC 00/14] usb: gadget: f_uac: Add support for alt mode settings crwulff
2024-09-28 15:08 ` [PATCH RFC 01/14] usb: gadget: f_uac: Refactor common configfs attribute defines used in UAC1/2 crwulff
@ 2024-09-28 15:08 ` crwulff
2024-09-28 15:08 ` [PATCH RFC 03/14] usb: gadget: f_uac1: Add adaptive sync support for capture crwulff
` (11 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: crwulff @ 2024-09-28 15:08 UTC (permalink / raw)
To: linux-usb
Cc: Pavel Hofman, Greg Kroah-Hartman, James Gruber, Jeff Johnson,
John Keeping, Jonathan Corbet, Lee Jones, Perr Zhang, linux-doc,
linux-kernel, Chris Wulff
From: Chris Wulff <crwulff@gmail.com>
This fixes two problems with the UAC1 descriptors.
bInterval for full-speed is now set to 1. Prior to this fix all speeds
were set to 4.
Super-speed descriptors are now built dynamically the same way as
the other speeds. The prior implementation had a fixed set of descriptors
and didn't take the presence of volume function units into account.
Both of these changes need the refactoring of setup_descriptor to
have a separate setup_header which is called for each speed. This
implementation closely follows what was done in f_uac2.
Fixes: b8fb6db6cb04 ("usb: f_uac1: adds support for SS and SSP")
Signed-off-by: Chris Wulff <crwulff@gmail.com>
---
drivers/usb/gadget/function/f_uac1.c | 248 ++++++++++++++++++++++-----
1 file changed, 202 insertions(+), 46 deletions(-)
diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c
index eeedcfa61fa1..f68d444d1961 100644
--- a/drivers/usb/gadget/function/f_uac1.c
+++ b/drivers/usb/gadget/function/f_uac1.c
@@ -128,7 +128,25 @@ static struct uac_feature_unit_descriptor *in_feature_unit_desc;
static struct uac_feature_unit_descriptor *out_feature_unit_desc;
/* AC IN Interrupt Endpoint */
-static struct usb_endpoint_descriptor ac_int_ep_desc = {
+static struct usb_endpoint_descriptor fs_ac_int_ep_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = cpu_to_le16(2),
+ .bInterval = 1,
+};
+
+static struct usb_endpoint_descriptor hs_ac_int_ep_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = cpu_to_le16(2),
+ .bInterval = 4,
+};
+
+static struct usb_endpoint_descriptor ss_ac_int_ep_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN,
@@ -137,6 +155,14 @@ static struct usb_endpoint_descriptor ac_int_ep_desc = {
.bInterval = 4,
};
+static struct usb_ss_ep_comp_descriptor ss_ac_int_ep_desc_comp = {
+ .bLength = sizeof(ss_ac_int_ep_desc_comp),
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+ .bMaxBurst = 0,
+ .bmAttributes = 0,
+ .wBytesPerInterval = cpu_to_le16(2),
+};
+
/* B.4.1 Standard AS Interface Descriptor */
static struct usb_interface_descriptor as_out_interface_alt_0_desc = {
.bLength = USB_DT_INTERFACE_SIZE,
@@ -208,7 +234,17 @@ static struct uac_format_type_i_discrete_descriptor as_out_type_i_desc = {
};
/* Standard ISO OUT Endpoint Descriptor */
-static struct usb_endpoint_descriptor as_out_ep_desc = {
+static struct usb_endpoint_descriptor fs_as_out_ep_desc = {
+ .bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_SYNC_ADAPTIVE
+ | USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
+ .bInterval = 1,
+};
+
+static struct usb_endpoint_descriptor hs_as_out_ep_desc = {
.bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_OUT,
@@ -238,8 +274,18 @@ static struct uac_format_type_i_discrete_descriptor as_in_type_i_desc = {
.bSamFreqType = 0, /* filled on rate setup */
};
-/* Standard ISO OUT Endpoint Descriptor */
-static struct usb_endpoint_descriptor as_in_ep_desc = {
+/* Standard ISO IN Endpoint Descriptor */
+static struct usb_endpoint_descriptor fs_as_in_ep_desc = {
+ .bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_SYNC_ASYNC
+ | USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
+ .bInterval = 1,
+};
+
+static struct usb_endpoint_descriptor hs_as_in_ep_desc = {
.bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN,
@@ -249,7 +295,7 @@ static struct usb_endpoint_descriptor as_in_ep_desc = {
.bInterval = 4,
};
-/* Class-specific AS ISO OUT Endpoint Descriptor */
+/* Class-specific AS ISO IN Endpoint Descriptor */
static struct uac_iso_endpoint_descriptor as_iso_in_desc = {
.bLength = UAC_ISO_ENDPOINT_DESC_SIZE,
.bDescriptorType = USB_DT_CS_ENDPOINT,
@@ -259,7 +305,41 @@ static struct uac_iso_endpoint_descriptor as_iso_in_desc = {
.wLockDelay = 0,
};
-static struct usb_descriptor_header *f_audio_desc[] = {
+static struct usb_descriptor_header *f_audio_fs_desc[] = {
+ (struct usb_descriptor_header *)&ac_interface_desc,
+ (struct usb_descriptor_header *)&ac_header_desc,
+
+ (struct usb_descriptor_header *)&usb_out_it_desc,
+ (struct usb_descriptor_header *)&io_out_ot_desc,
+ (struct usb_descriptor_header *)&out_feature_unit_desc,
+
+ (struct usb_descriptor_header *)&io_in_it_desc,
+ (struct usb_descriptor_header *)&usb_in_ot_desc,
+ (struct usb_descriptor_header *)&in_feature_unit_desc,
+
+ (struct usb_descriptor_header *)&fs_ac_int_ep_desc,
+
+ (struct usb_descriptor_header *)&as_out_interface_alt_0_desc,
+ (struct usb_descriptor_header *)&as_out_interface_alt_1_desc,
+ (struct usb_descriptor_header *)&as_out_header_desc,
+
+ (struct usb_descriptor_header *)&as_out_type_i_desc,
+
+ (struct usb_descriptor_header *)&fs_as_out_ep_desc,
+ (struct usb_descriptor_header *)&as_iso_out_desc,
+
+ (struct usb_descriptor_header *)&as_in_interface_alt_0_desc,
+ (struct usb_descriptor_header *)&as_in_interface_alt_1_desc,
+ (struct usb_descriptor_header *)&as_in_header_desc,
+
+ (struct usb_descriptor_header *)&as_in_type_i_desc,
+
+ (struct usb_descriptor_header *)&fs_as_in_ep_desc,
+ (struct usb_descriptor_header *)&as_iso_in_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *f_audio_hs_desc[] = {
(struct usb_descriptor_header *)&ac_interface_desc,
(struct usb_descriptor_header *)&ac_header_desc,
@@ -271,7 +351,7 @@ static struct usb_descriptor_header *f_audio_desc[] = {
(struct usb_descriptor_header *)&usb_in_ot_desc,
(struct usb_descriptor_header *)&in_feature_unit_desc,
- (struct usb_descriptor_header *)&ac_int_ep_desc,
+ (struct usb_descriptor_header *)&hs_ac_int_ep_desc,
(struct usb_descriptor_header *)&as_out_interface_alt_0_desc,
(struct usb_descriptor_header *)&as_out_interface_alt_1_desc,
@@ -279,7 +359,7 @@ static struct usb_descriptor_header *f_audio_desc[] = {
(struct usb_descriptor_header *)&as_out_type_i_desc,
- (struct usb_descriptor_header *)&as_out_ep_desc,
+ (struct usb_descriptor_header *)&hs_as_out_ep_desc,
(struct usb_descriptor_header *)&as_iso_out_desc,
(struct usb_descriptor_header *)&as_in_interface_alt_0_desc,
@@ -288,7 +368,7 @@ static struct usb_descriptor_header *f_audio_desc[] = {
(struct usb_descriptor_header *)&as_in_type_i_desc,
- (struct usb_descriptor_header *)&as_in_ep_desc,
+ (struct usb_descriptor_header *)&hs_as_in_ep_desc,
(struct usb_descriptor_header *)&as_iso_in_desc,
NULL,
};
@@ -312,7 +392,7 @@ static struct usb_ss_ep_comp_descriptor ss_as_out_ep_desc_comp = {
/* wBytesPerInterval = DYNAMIC */
};
-/* Standard ISO OUT Endpoint Descriptor */
+/* Standard ISO IN Endpoint Descriptor */
static struct usb_endpoint_descriptor ss_as_in_ep_desc = {
.bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
@@ -337,8 +417,14 @@ static struct usb_descriptor_header *f_audio_ss_desc[] = {
(struct usb_descriptor_header *)&usb_out_it_desc,
(struct usb_descriptor_header *)&io_out_ot_desc,
+ (struct usb_descriptor_header *)&out_feature_unit_desc,
+
(struct usb_descriptor_header *)&io_in_it_desc,
(struct usb_descriptor_header *)&usb_in_ot_desc,
+ (struct usb_descriptor_header *)&in_feature_unit_desc,
+
+ (struct usb_descriptor_header *)&ss_ac_int_ep_desc,
+ (struct usb_descriptor_header *)&ss_ac_int_ep_desc_comp,
(struct usb_descriptor_header *)&as_out_interface_alt_0_desc,
(struct usb_descriptor_header *)&as_out_interface_alt_1_desc,
@@ -346,7 +432,6 @@ static struct usb_descriptor_header *f_audio_ss_desc[] = {
(struct usb_descriptor_header *)&as_out_type_i_desc,
- //(struct usb_descriptor_header *)&as_out_ep_desc,
(struct usb_descriptor_header *)&ss_as_out_ep_desc,
(struct usb_descriptor_header *)&ss_as_out_ep_desc_comp,
(struct usb_descriptor_header *)&as_iso_out_desc,
@@ -357,7 +442,6 @@ static struct usb_descriptor_header *f_audio_ss_desc[] = {
(struct usb_descriptor_header *)&as_in_type_i_desc,
- //(struct usb_descriptor_header *)&as_in_ep_desc,
(struct usb_descriptor_header *)&ss_as_in_ep_desc,
(struct usb_descriptor_header *)&ss_as_in_ep_desc_comp,
(struct usb_descriptor_header *)&as_iso_in_desc,
@@ -1082,6 +1166,10 @@ uac1_ac_header_descriptor *build_ac_header_desc(struct f_uac1_opts *opts)
/* Use macro to overcome line length limitation */
#define USBDHDR(p) (struct usb_descriptor_header *)(p)
+static void setup_headers(struct f_uac1_opts *opts,
+ struct usb_descriptor_header **headers,
+ enum usb_device_speed speed);
+
static void setup_descriptor(struct f_uac1_opts *opts)
{
/* patch descriptors */
@@ -1137,44 +1225,90 @@ static void setup_descriptor(struct f_uac1_opts *opts)
ac_header_desc->wTotalLength = cpu_to_le16(len);
}
+ setup_headers(opts, f_audio_fs_desc, USB_SPEED_FULL);
+ setup_headers(opts, f_audio_hs_desc, USB_SPEED_HIGH);
+ setup_headers(opts, f_audio_ss_desc, USB_SPEED_SUPER);
+}
+
+static void setup_headers(struct f_uac1_opts *opts,
+ struct usb_descriptor_header **headers,
+ enum usb_device_speed speed)
+{
+ struct usb_ss_ep_comp_descriptor *epout_desc_comp = NULL;
+ struct usb_ss_ep_comp_descriptor *epin_desc_comp = NULL;
+ struct usb_ss_ep_comp_descriptor *ep_int_desc_comp = NULL;
+ struct usb_endpoint_descriptor *epout_desc;
+ struct usb_endpoint_descriptor *epin_desc;
+ struct usb_endpoint_descriptor *ep_int_desc;
+ int i;
+
+ switch (speed) {
+ case USB_SPEED_FULL:
+ epout_desc = &fs_as_out_ep_desc;
+ epin_desc = &fs_as_in_ep_desc;
+ ep_int_desc = &fs_ac_int_ep_desc;
+ break;
+ case USB_SPEED_HIGH:
+ epout_desc = &hs_as_out_ep_desc;
+ epin_desc = &hs_as_in_ep_desc;
+ ep_int_desc = &hs_ac_int_ep_desc;
+ break;
+ default:
+ epout_desc = &ss_as_out_ep_desc;
+ epin_desc = &ss_as_in_ep_desc;
+ epout_desc_comp = &ss_as_out_ep_desc_comp;
+ epin_desc_comp = &ss_as_in_ep_desc_comp;
+ ep_int_desc = &ss_ac_int_ep_desc;
+ ep_int_desc_comp = &ss_ac_int_ep_desc_comp;
+ }
+
i = 0;
- f_audio_desc[i++] = USBDHDR(&ac_interface_desc);
- f_audio_desc[i++] = USBDHDR(ac_header_desc);
+ headers[i++] = USBDHDR(&ac_interface_desc);
+ headers[i++] = USBDHDR(ac_header_desc);
if (EPOUT_EN(opts)) {
- f_audio_desc[i++] = USBDHDR(&usb_out_it_desc);
- f_audio_desc[i++] = USBDHDR(&io_out_ot_desc);
+ headers[i++] = USBDHDR(&usb_out_it_desc);
+ headers[i++] = USBDHDR(&io_out_ot_desc);
if (FUOUT_EN(opts))
- f_audio_desc[i++] = USBDHDR(out_feature_unit_desc);
+ headers[i++] = USBDHDR(out_feature_unit_desc);
}
if (EPIN_EN(opts)) {
- f_audio_desc[i++] = USBDHDR(&io_in_it_desc);
- f_audio_desc[i++] = USBDHDR(&usb_in_ot_desc);
+ headers[i++] = USBDHDR(&io_in_it_desc);
+ headers[i++] = USBDHDR(&usb_in_ot_desc);
if (FUIN_EN(opts))
- f_audio_desc[i++] = USBDHDR(in_feature_unit_desc);
+ headers[i++] = USBDHDR(in_feature_unit_desc);
}
- if (FUOUT_EN(opts) || FUIN_EN(opts))
- f_audio_desc[i++] = USBDHDR(&ac_int_ep_desc);
+ if (FUOUT_EN(opts) || FUIN_EN(opts)) {
+ headers[i++] = USBDHDR(ep_int_desc);
+ if (ep_int_desc_comp)
+ headers[i++] = USBDHDR(ep_int_desc_comp);
+ }
if (EPOUT_EN(opts)) {
- f_audio_desc[i++] = USBDHDR(&as_out_interface_alt_0_desc);
- f_audio_desc[i++] = USBDHDR(&as_out_interface_alt_1_desc);
- f_audio_desc[i++] = USBDHDR(&as_out_header_desc);
- f_audio_desc[i++] = USBDHDR(&as_out_type_i_desc);
- f_audio_desc[i++] = USBDHDR(&as_out_ep_desc);
- f_audio_desc[i++] = USBDHDR(&as_iso_out_desc);
+ headers[i++] = USBDHDR(&as_out_interface_alt_0_desc);
+ headers[i++] = USBDHDR(&as_out_interface_alt_1_desc);
+ headers[i++] = USBDHDR(&as_out_header_desc);
+ headers[i++] = USBDHDR(&as_out_type_i_desc);
+ headers[i++] = USBDHDR(epout_desc);
+ if (epout_desc_comp)
+ headers[i++] = USBDHDR(epout_desc_comp);
+
+ headers[i++] = USBDHDR(&as_iso_out_desc);
}
if (EPIN_EN(opts)) {
- f_audio_desc[i++] = USBDHDR(&as_in_interface_alt_0_desc);
- f_audio_desc[i++] = USBDHDR(&as_in_interface_alt_1_desc);
- f_audio_desc[i++] = USBDHDR(&as_in_header_desc);
- f_audio_desc[i++] = USBDHDR(&as_in_type_i_desc);
- f_audio_desc[i++] = USBDHDR(&as_in_ep_desc);
- f_audio_desc[i++] = USBDHDR(&as_iso_in_desc);
+ headers[i++] = USBDHDR(&as_in_interface_alt_0_desc);
+ headers[i++] = USBDHDR(&as_in_interface_alt_1_desc);
+ headers[i++] = USBDHDR(&as_in_header_desc);
+ headers[i++] = USBDHDR(&as_in_type_i_desc);
+ headers[i++] = USBDHDR(epin_desc);
+ if (epin_desc_comp)
+ headers[i++] = USBDHDR(epin_desc_comp);
+
+ headers[i++] = USBDHDR(&as_iso_in_desc);
}
- f_audio_desc[i] = NULL;
+ headers[i] = NULL;
}
static int f_audio_validate_opts(struct g_audio *audio, struct device *dev)
@@ -1410,44 +1544,66 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
/* allocate AC interrupt endpoint */
if (FUOUT_EN(audio_opts) || FUIN_EN(audio_opts)) {
- ep = usb_ep_autoconfig(cdev->gadget, &ac_int_ep_desc);
+ ep = usb_ep_autoconfig(cdev->gadget, &fs_ac_int_ep_desc);
if (!ep)
goto err_free_fu;
+
+ hs_ac_int_ep_desc.bEndpointAddress = fs_ac_int_ep_desc.bEndpointAddress;
+ ss_ac_int_ep_desc.bEndpointAddress = fs_ac_int_ep_desc.bEndpointAddress;
+
uac1->int_ep = ep;
- uac1->int_ep->desc = &ac_int_ep_desc;
+ uac1->int_ep->desc = &fs_ac_int_ep_desc;
ac_interface_desc.bNumEndpoints = 1;
}
/* allocate instance-specific endpoints */
if (EPOUT_EN(audio_opts)) {
- ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc);
+ ep = usb_ep_autoconfig(cdev->gadget, &fs_as_out_ep_desc);
if (!ep)
goto err_free_fu;
- ss_as_out_ep_desc.bEndpointAddress = as_out_ep_desc.bEndpointAddress;
+
+ hs_as_out_ep_desc.bEndpointAddress = fs_as_out_ep_desc.bEndpointAddress;
+ ss_as_out_ep_desc.bEndpointAddress = fs_as_out_ep_desc.bEndpointAddress;
+ ss_as_out_ep_desc_comp.wBytesPerInterval = ss_as_out_ep_desc.wMaxPacketSize;
+
audio->out_ep = ep;
- audio->out_ep->desc = &as_out_ep_desc;
+ audio->out_ep->desc = &fs_as_out_ep_desc;
}
if (EPIN_EN(audio_opts)) {
- ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc);
+ ep = usb_ep_autoconfig(cdev->gadget, &fs_as_in_ep_desc);
if (!ep)
goto err_free_fu;
- ss_as_in_ep_desc.bEndpointAddress = as_in_ep_desc.bEndpointAddress;
+
+ hs_as_in_ep_desc.bEndpointAddress = fs_as_in_ep_desc.bEndpointAddress;
+ ss_as_in_ep_desc.bEndpointAddress = fs_as_in_ep_desc.bEndpointAddress;
+ ss_as_in_ep_desc_comp.wBytesPerInterval = ss_as_in_ep_desc.wMaxPacketSize;
+
audio->in_ep = ep;
- audio->in_ep->desc = &as_in_ep_desc;
+ audio->in_ep->desc = &fs_as_in_ep_desc;
}
setup_descriptor(audio_opts);
/* copy descriptors, and track endpoint copies */
- status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, f_audio_ss_desc,
+ status = usb_assign_descriptors(f, f_audio_fs_desc, f_audio_hs_desc, f_audio_ss_desc,
f_audio_ss_desc);
if (status)
goto err_free_fu;
- audio->out_ep_maxpsize = le16_to_cpu(as_out_ep_desc.wMaxPacketSize);
- audio->in_ep_maxpsize = le16_to_cpu(as_in_ep_desc.wMaxPacketSize);
+ audio->in_ep_maxpsize = max_t(u16,
+ le16_to_cpu(fs_as_in_ep_desc.wMaxPacketSize),
+ le16_to_cpu(hs_as_in_ep_desc.wMaxPacketSize));
+ audio->out_ep_maxpsize = max_t(u16,
+ le16_to_cpu(fs_as_out_ep_desc.wMaxPacketSize),
+ le16_to_cpu(hs_as_out_ep_desc.wMaxPacketSize));
+
+ audio->in_ep_maxpsize = max_t(u16, audio->in_ep_maxpsize,
+ le16_to_cpu(ss_as_in_ep_desc.wMaxPacketSize));
+ audio->out_ep_maxpsize = max_t(u16, audio->out_ep_maxpsize,
+ le16_to_cpu(ss_as_out_ep_desc.wMaxPacketSize));
+
audio->params.c_chmask = audio_opts->c_chmask;
memcpy(audio->params.c_srates, audio_opts->c_srates,
sizeof(audio->params.c_srates));
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH RFC 03/14] usb: gadget: f_uac1: Add adaptive sync support for capture
2024-09-28 15:08 [PATCH RFC 00/14] usb: gadget: f_uac: Add support for alt mode settings crwulff
2024-09-28 15:08 ` [PATCH RFC 01/14] usb: gadget: f_uac: Refactor common configfs attribute defines used in UAC1/2 crwulff
2024-09-28 15:08 ` [PATCH RFC 02/14] usb: gadget: f_uac1: Fix fs/hs/ss descriptors to have correct values crwulff
@ 2024-09-28 15:08 ` crwulff
2024-09-28 15:08 ` [PATCH RFC 04/14] usb: gadget: f_uac2: Move max packet size code to a common header crwulff
` (10 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: crwulff @ 2024-09-28 15:08 UTC (permalink / raw)
To: linux-usb
Cc: Pavel Hofman, Greg Kroah-Hartman, James Gruber, Jeff Johnson,
John Keeping, Jonathan Corbet, Lee Jones, Perr Zhang, linux-doc,
linux-kernel, Chris Wulff
From: Chris Wulff <crwulff@gmail.com>
Allow the user to configure async or adaptive mode for data from
the host. Enabling async mode will include the feedback endpoint.
This functions the same as UAC2.
Signed-off-by: Chris Wulff <crwulff@gmail.com>
---
.../ABI/testing/configfs-usb-gadget-uac1 | 3 +
Documentation/usb/gadget-testing.rst | 2 +
drivers/usb/gadget/function/f_uac1.c | 101 +++++++++++++++++-
drivers/usb/gadget/function/u_uac1.h | 3 +
4 files changed, 108 insertions(+), 1 deletion(-)
diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac1 b/Documentation/ABI/testing/configfs-usb-gadget-uac1
index 64188a85592b..758b8c9a988a 100644
--- a/Documentation/ABI/testing/configfs-usb-gadget-uac1
+++ b/Documentation/ABI/testing/configfs-usb-gadget-uac1
@@ -8,6 +8,8 @@ Description:
c_chmask capture channel mask
c_srate list of capture sampling rates (comma-separated)
c_ssize capture sample size (bytes)
+ c_sync capture synchronization type
+ (async/adaptive)
c_mute_present capture mute control enable
c_volume_present capture volume control enable
c_volume_min capture volume control min value
@@ -16,6 +18,7 @@ Description:
(in 1/256 dB)
c_volume_res capture volume control resolution
(in 1/256 dB)
+ fb_max maximum extra bandwidth in async mode
p_chmask playback channel mask
p_srate list of playback sampling rates (comma-separated)
p_ssize playback sample size (bytes)
diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst
index bf555c2270f5..68fc0011b388 100644
--- a/Documentation/usb/gadget-testing.rst
+++ b/Documentation/usb/gadget-testing.rst
@@ -952,11 +952,13 @@ The uac1 function provides these attributes in its function directory:
c_chmask capture channel mask
c_srate list of capture sampling rates (comma-separated)
c_ssize capture sample size (bytes)
+ c_sync capture synchronization type (async/adaptive)
c_mute_present capture mute control enable
c_volume_present capture volume control enable
c_volume_min capture volume control min value (in 1/256 dB)
c_volume_max capture volume control max value (in 1/256 dB)
c_volume_res capture volume control resolution (in 1/256 dB)
+ fb_max maximum extra bandwidth in async mode
p_chmask playback channel mask
p_srate list of playback sampling rates (comma-separated)
p_ssize playback sample size (bytes)
diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c
index f68d444d1961..84423d9a8bd7 100644
--- a/drivers/usb/gadget/function/f_uac1.c
+++ b/drivers/usb/gadget/function/f_uac1.c
@@ -33,6 +33,7 @@
|| (_opts)->p_volume_present)
#define FUOUT_EN(_opts) ((_opts)->c_mute_present \
|| (_opts)->c_volume_present)
+#define EPOUT_FBACK_IN_EN(_opts) ((_opts)->c_sync == USB_ENDPOINT_SYNC_ASYNC)
struct f_uac1 {
struct g_audio g_audio;
@@ -305,6 +306,48 @@ static struct uac_iso_endpoint_descriptor as_iso_in_desc = {
.wLockDelay = 0,
};
+/* STD AS ISO IN Feedback Endpoint */
+static struct usb_endpoint_descriptor fs_as_in_fback_desc = {
+ .bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_USAGE_FEEDBACK,
+ .wMaxPacketSize = cpu_to_le16(3),
+ .bInterval = 1,
+ .bRefresh = 0,
+ .bSynchAddress = 0,
+};
+
+static struct usb_endpoint_descriptor hs_as_in_fback_desc = {
+ .bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_USAGE_FEEDBACK,
+ .wMaxPacketSize = cpu_to_le16(4),
+ .bInterval = 4,
+ .bRefresh = 0,
+ .bSynchAddress = 0,
+};
+
+static struct usb_endpoint_descriptor ss_as_in_fback_desc = {
+ .bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_USAGE_FEEDBACK,
+ .wMaxPacketSize = cpu_to_le16(4),
+ .bInterval = 4,
+ .bRefresh = 0,
+ .bSynchAddress = 0,
+};
+
+static struct usb_ss_ep_comp_descriptor ss_as_in_fback_desc_comp = {
+ .bLength = sizeof(ss_as_in_fback_desc_comp),
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+ .bMaxBurst = 0,
+ .bmAttributes = 0,
+ .wBytesPerInterval = cpu_to_le16(4),
+};
+
static struct usb_descriptor_header *f_audio_fs_desc[] = {
(struct usb_descriptor_header *)&ac_interface_desc,
(struct usb_descriptor_header *)&ac_header_desc,
@@ -327,6 +370,7 @@ static struct usb_descriptor_header *f_audio_fs_desc[] = {
(struct usb_descriptor_header *)&fs_as_out_ep_desc,
(struct usb_descriptor_header *)&as_iso_out_desc,
+ (struct usb_descriptor_header *)&fs_as_in_fback_desc,
(struct usb_descriptor_header *)&as_in_interface_alt_0_desc,
(struct usb_descriptor_header *)&as_in_interface_alt_1_desc,
@@ -361,6 +405,7 @@ static struct usb_descriptor_header *f_audio_hs_desc[] = {
(struct usb_descriptor_header *)&hs_as_out_ep_desc,
(struct usb_descriptor_header *)&as_iso_out_desc,
+ (struct usb_descriptor_header *)&hs_as_in_fback_desc,
(struct usb_descriptor_header *)&as_in_interface_alt_0_desc,
(struct usb_descriptor_header *)&as_in_interface_alt_1_desc,
@@ -435,6 +480,8 @@ static struct usb_descriptor_header *f_audio_ss_desc[] = {
(struct usb_descriptor_header *)&ss_as_out_ep_desc,
(struct usb_descriptor_header *)&ss_as_out_ep_desc_comp,
(struct usb_descriptor_header *)&as_iso_out_desc,
+ (struct usb_descriptor_header *)&ss_as_in_fback_desc,
+ (struct usb_descriptor_header *)&ss_as_in_fback_desc_comp,
(struct usb_descriptor_header *)&as_in_interface_alt_0_desc,
(struct usb_descriptor_header *)&as_in_interface_alt_1_desc,
@@ -1236,9 +1283,11 @@ static void setup_headers(struct f_uac1_opts *opts,
{
struct usb_ss_ep_comp_descriptor *epout_desc_comp = NULL;
struct usb_ss_ep_comp_descriptor *epin_desc_comp = NULL;
+ struct usb_ss_ep_comp_descriptor *epin_fback_desc_comp = NULL;
struct usb_ss_ep_comp_descriptor *ep_int_desc_comp = NULL;
struct usb_endpoint_descriptor *epout_desc;
struct usb_endpoint_descriptor *epin_desc;
+ struct usb_endpoint_descriptor *epin_fback_desc;
struct usb_endpoint_descriptor *ep_int_desc;
int i;
@@ -1246,11 +1295,13 @@ static void setup_headers(struct f_uac1_opts *opts,
case USB_SPEED_FULL:
epout_desc = &fs_as_out_ep_desc;
epin_desc = &fs_as_in_ep_desc;
+ epin_fback_desc = &fs_as_in_fback_desc;
ep_int_desc = &fs_ac_int_ep_desc;
break;
case USB_SPEED_HIGH:
epout_desc = &hs_as_out_ep_desc;
epin_desc = &hs_as_in_ep_desc;
+ epin_fback_desc = &hs_as_in_fback_desc;
ep_int_desc = &hs_ac_int_ep_desc;
break;
default:
@@ -1258,6 +1309,8 @@ static void setup_headers(struct f_uac1_opts *opts,
epin_desc = &ss_as_in_ep_desc;
epout_desc_comp = &ss_as_out_ep_desc_comp;
epin_desc_comp = &ss_as_in_ep_desc_comp;
+ epin_fback_desc = &ss_as_in_fback_desc;
+ epin_fback_desc_comp = &ss_as_in_fback_desc_comp;
ep_int_desc = &ss_ac_int_ep_desc;
ep_int_desc_comp = &ss_ac_int_ep_desc_comp;
}
@@ -1296,6 +1349,12 @@ static void setup_headers(struct f_uac1_opts *opts,
headers[i++] = USBDHDR(epout_desc_comp);
headers[i++] = USBDHDR(&as_iso_out_desc);
+
+ if (EPOUT_FBACK_IN_EN(opts)) {
+ headers[i++] = USBDHDR(epin_fback_desc);
+ if (epin_fback_desc_comp)
+ headers[i++] = USBDHDR(epin_fback_desc_comp);
+ }
}
if (EPIN_EN(opts)) {
headers[i++] = USBDHDR(&as_in_interface_alt_0_desc);
@@ -1509,6 +1568,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
if (status < 0)
goto err_free_fu;
ac_interface_desc.bInterfaceNumber = status;
+ ac_interface_desc.bNumEndpoints = 1;
uac1->ac_intf = status;
uac1->ac_alt = 0;
@@ -1523,6 +1583,23 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
ac_header_desc->baInterfaceNr[ba_iface_id++] = status;
uac1->as_out_intf = status;
uac1->as_out_alt = 0;
+
+ if (EPOUT_FBACK_IN_EN(audio_opts)) {
+ fs_as_out_ep_desc.bmAttributes =
+ USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC;
+ hs_as_out_ep_desc.bmAttributes =
+ USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC;
+ ss_as_out_ep_desc.bmAttributes =
+ USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC;
+ ac_interface_desc.bNumEndpoints++;
+ } else {
+ fs_as_out_ep_desc.bmAttributes =
+ USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE;
+ hs_as_out_ep_desc.bmAttributes =
+ USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE;
+ ss_as_out_ep_desc.bmAttributes =
+ USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE;
+ }
}
if (EPIN_EN(audio_opts)) {
@@ -1569,6 +1646,17 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
audio->out_ep = ep;
audio->out_ep->desc = &fs_as_out_ep_desc;
+
+ if (EPOUT_FBACK_IN_EN(audio_opts)) {
+ ep = usb_ep_autoconfig(cdev->gadget, &fs_as_in_fback_desc);
+ if (!ep)
+ goto err_free_fu;
+
+ hs_as_in_fback_desc.bEndpointAddress = fs_as_in_fback_desc.bEndpointAddress;
+ ss_as_in_fback_desc.bEndpointAddress = fs_as_in_fback_desc.bEndpointAddress;
+
+ audio->in_ep_fback = ep;
+ }
}
if (EPIN_EN(audio_opts)) {
@@ -1631,7 +1719,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
audio->params.c_fu.volume_res = audio_opts->c_volume_res;
}
audio->params.req_number = audio_opts->req_number;
- audio->params.fb_max = FBACK_FAST_MAX;
+ audio->params.fb_max = audio_opts->fb_max;
if (FUOUT_EN(audio_opts) || FUIN_EN(audio_opts))
audio->notify = audio_notify;
@@ -1678,6 +1766,10 @@ static struct configfs_item_operations f_uac1_item_ops = {
UAC_ATTRIBUTE(f_uac1_opts, UAC1_ATTR_TO_OPTS, opts, \
opts->lock, opts->refcnt, type, name)
+#define UAC1_ATTRIBUTE_SYNC(name) \
+ UAC_ATTRIBUTE_SYNC(f_uac1_opts, UAC1_ATTR_TO_OPTS, opts, \
+ opts->lock, opts->refcnt, name)
+
#define UAC1_RATE_ATTRIBUTE(name) \
UAC_RATE_ATTRIBUTE(f_uac1_opts, UAC1_ATTR_TO_OPTS, opts, \
opts->lock, opts->refcnt, name)
@@ -1688,6 +1780,7 @@ static struct configfs_item_operations f_uac1_item_ops = {
UAC1_ATTRIBUTE(u32, c_chmask);
UAC1_RATE_ATTRIBUTE(c_srate);
+UAC1_ATTRIBUTE_SYNC(c_sync);
UAC1_ATTRIBUTE(u32, c_ssize);
UAC1_ATTRIBUTE(u32, p_chmask);
UAC1_RATE_ATTRIBUTE(p_srate);
@@ -1706,6 +1799,8 @@ UAC1_ATTRIBUTE(s16, c_volume_min);
UAC1_ATTRIBUTE(s16, c_volume_max);
UAC1_ATTRIBUTE(s16, c_volume_res);
+UAC1_ATTRIBUTE(u32, fb_max);
+
UAC1_ATTRIBUTE_STRING(function_name);
UAC1_ATTRIBUTE_STRING(p_it_name);
@@ -1721,11 +1816,13 @@ UAC1_ATTRIBUTE_STRING(c_fu_vol_name);
static struct configfs_attribute *f_uac1_attrs[] = {
&f_uac1_opts_attr_c_chmask,
&f_uac1_opts_attr_c_srate,
+ &f_uac1_opts_attr_c_sync,
&f_uac1_opts_attr_c_ssize,
&f_uac1_opts_attr_p_chmask,
&f_uac1_opts_attr_p_srate,
&f_uac1_opts_attr_p_ssize,
&f_uac1_opts_attr_req_number,
+ &f_uac1_opts_attr_fb_max,
&f_uac1_opts_attr_p_mute_present,
&f_uac1_opts_attr_p_volume_present,
@@ -1784,6 +1881,7 @@ static struct usb_function_instance *f_audio_alloc_inst(void)
opts->c_chmask = UAC1_DEF_CCHMASK;
opts->c_srates[0] = UAC1_DEF_CSRATE;
+ opts->c_sync = UAC1_DEF_CSYNC;
opts->c_ssize = UAC1_DEF_CSSIZE;
opts->p_chmask = UAC1_DEF_PCHMASK;
opts->p_srates[0] = UAC1_DEF_PSRATE;
@@ -1802,6 +1900,7 @@ static struct usb_function_instance *f_audio_alloc_inst(void)
opts->c_volume_res = UAC1_DEF_RES_DB;
opts->req_number = UAC1_DEF_REQ_NUM;
+ opts->fb_max = FBACK_FAST_MAX;
scnprintf(opts->function_name, sizeof(opts->function_name), "AC Interface");
diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h
index feb6eb76462f..59eac5ca8114 100644
--- a/drivers/usb/gadget/function/u_uac1.h
+++ b/drivers/usb/gadget/function/u_uac1.h
@@ -14,6 +14,7 @@
#define UAC1_OUT_EP_MAX_PACKET_SIZE 200
#define UAC1_DEF_CCHMASK 0x3
#define UAC1_DEF_CSRATE 48000
+#define UAC1_DEF_CSYNC USB_ENDPOINT_SYNC_ADAPTIVE
#define UAC1_DEF_CSSIZE 2
#define UAC1_DEF_PCHMASK 0x3
#define UAC1_DEF_PSRATE 48000
@@ -32,6 +33,7 @@ struct f_uac1_opts {
struct usb_function_instance func_inst;
int c_chmask;
int c_srates[UAC_MAX_RATES];
+ int c_sync;
int c_ssize;
int p_chmask;
int p_srates[UAC_MAX_RATES];
@@ -50,6 +52,7 @@ struct f_uac1_opts {
s16 c_volume_res;
int req_number;
+ int fb_max;
unsigned bound:1;
char function_name[USB_MAX_STRING_LEN];
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH RFC 04/14] usb: gadget: f_uac2: Move max packet size code to a common header
2024-09-28 15:08 [PATCH RFC 00/14] usb: gadget: f_uac: Add support for alt mode settings crwulff
` (2 preceding siblings ...)
2024-09-28 15:08 ` [PATCH RFC 03/14] usb: gadget: f_uac1: Add adaptive sync support for capture crwulff
@ 2024-09-28 15:08 ` crwulff
2024-09-28 15:08 ` [PATCH RFC 05/14] usb: gadget: f_uac1: Add hs_bint to configfs crwulff
` (9 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: crwulff @ 2024-09-28 15:08 UTC (permalink / raw)
To: linux-usb
Cc: Pavel Hofman, Greg Kroah-Hartman, James Gruber, Jeff Johnson,
John Keeping, Jonathan Corbet, Lee Jones, Perr Zhang, linux-doc,
linux-kernel, Chris Wulff
From: Chris Wulff <crwulff@gmail.com>
Moving this to allow it to be used by f_uac1 in the future.
Signed-off-by: Chris Wulff <crwulff@gmail.com>
---
drivers/usb/gadget/function/f_uac2.c | 116 ++--------------------
drivers/usb/gadget/function/u_uac_utils.h | 111 +++++++++++++++++++++
2 files changed, 122 insertions(+), 105 deletions(-)
diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index 2142a0951f71..050789d2d3c9 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -112,16 +112,6 @@ enum {
static struct usb_string strings_fn[NUM_STR_DESCRIPTORS + 1] = {};
-static const char *const speed_names[] = {
- [USB_SPEED_UNKNOWN] = "UNKNOWN",
- [USB_SPEED_LOW] = "LS",
- [USB_SPEED_FULL] = "FS",
- [USB_SPEED_HIGH] = "HS",
- [USB_SPEED_WIRELESS] = "W",
- [USB_SPEED_SUPER] = "SS",
- [USB_SPEED_SUPER_PLUS] = "SS+",
-};
-
static struct usb_gadget_strings str_fn = {
.language = 0x0409, /* en-us */
.strings = strings_fn,
@@ -656,113 +646,29 @@ struct cntrl_subrange_lay3 {
DECLARE_UAC2_CNTRL_RANGES_LAY3(srates, UAC_MAX_RATES);
-static int get_max_srate(const int *srates)
-{
- int i, max_srate = 0;
-
- for (i = 0; i < UAC_MAX_RATES; i++) {
- if (srates[i] == 0)
- break;
- if (srates[i] > max_srate)
- max_srate = srates[i];
- }
- return max_srate;
-}
-
-static int get_max_bw_for_bint(const struct f_uac2_opts *uac2_opts,
- u8 bint, unsigned int factor, bool is_playback)
+static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac2_opts *uac2_opts,
+ struct usb_endpoint_descriptor *ep_desc,
+ enum usb_device_speed speed, bool is_playback)
{
- int chmask, srate, ssize;
- u16 max_size_bw;
+ int chmask, srate, ssize, hs_bint, sync;
if (is_playback) {
chmask = uac2_opts->p_chmask;
srate = get_max_srate(uac2_opts->p_srates);
ssize = uac2_opts->p_ssize;
+ hs_bint = uac2_opts->p_hs_bint;
+ sync = USB_ENDPOINT_SYNC_ASYNC;
} else {
chmask = uac2_opts->c_chmask;
srate = get_max_srate(uac2_opts->c_srates);
ssize = uac2_opts->c_ssize;
+ hs_bint = uac2_opts->c_hs_bint;
+ sync = uac2_opts->c_sync;
}
- if (is_playback || (uac2_opts->c_sync == USB_ENDPOINT_SYNC_ASYNC)) {
- // playback is always async, capture only when configured
- // Win10 requires max packet size + 1 frame
- srate = srate * (1000 + uac2_opts->fb_max) / 1000;
- // updated srate is always bigger, therefore DIV_ROUND_UP always yields +1
- max_size_bw = num_channels(chmask) * ssize *
- (DIV_ROUND_UP(srate, factor / (1 << (bint - 1))));
- } else {
- // adding 1 frame provision for Win10
- max_size_bw = num_channels(chmask) * ssize *
- (DIV_ROUND_UP(srate, factor / (1 << (bint - 1))) + 1);
- }
- return max_size_bw;
-}
-
-static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac2_opts *uac2_opts,
- struct usb_endpoint_descriptor *ep_desc,
- enum usb_device_speed speed, bool is_playback)
-{
- u16 max_size_bw, max_size_ep;
- u8 bint, opts_bint;
- char *dir;
-
- switch (speed) {
- case USB_SPEED_FULL:
- max_size_ep = 1023;
- // fixed
- bint = ep_desc->bInterval;
- max_size_bw = get_max_bw_for_bint(uac2_opts, bint, 1000, is_playback);
- break;
-
- case USB_SPEED_HIGH:
- case USB_SPEED_SUPER:
- max_size_ep = 1024;
- if (is_playback)
- opts_bint = uac2_opts->p_hs_bint;
- else
- opts_bint = uac2_opts->c_hs_bint;
-
- if (opts_bint > 0) {
- /* fixed bint */
- bint = opts_bint;
- max_size_bw = get_max_bw_for_bint(uac2_opts, bint, 8000, is_playback);
- } else {
- /* checking bInterval from 4 to 1 whether the required bandwidth fits */
- for (bint = 4; bint > 0; --bint) {
- max_size_bw = get_max_bw_for_bint(
- uac2_opts, bint, 8000, is_playback);
- if (max_size_bw <= max_size_ep)
- break;
- }
- }
- break;
-
- default:
- return -EINVAL;
- }
-
- if (is_playback)
- dir = "Playback";
- else
- dir = "Capture";
-
- if (max_size_bw <= max_size_ep)
- dev_dbg(dev,
- "%s %s: Would use wMaxPacketSize %d and bInterval %d\n",
- speed_names[speed], dir, max_size_bw, bint);
- else {
- dev_warn(dev,
- "%s %s: Req. wMaxPacketSize %d at bInterval %d > max ISOC %d, may drop data!\n",
- speed_names[speed], dir, max_size_bw, bint, max_size_ep);
- max_size_bw = max_size_ep;
- }
-
- ep_desc->wMaxPacketSize = cpu_to_le16(max_size_bw);
- ep_desc->bInterval = bint;
-
- return 0;
+ return uac_set_ep_max_packet_size_bint(
+ dev, ep_desc, speed, is_playback, hs_bint, chmask,
+ srate, ssize, sync, uac2_opts->fb_max);
}
static struct uac2_feature_unit_descriptor *build_fu_desc(int chmask)
diff --git a/drivers/usb/gadget/function/u_uac_utils.h b/drivers/usb/gadget/function/u_uac_utils.h
index 5f88e31103dd..7ef9f699657c 100644
--- a/drivers/usb/gadget/function/u_uac_utils.h
+++ b/drivers/usb/gadget/function/u_uac_utils.h
@@ -214,4 +214,115 @@ end: \
\
CONFIGFS_ATTR(prefix##_, name)
+/*
+ * Functions for EP interval and max packet size
+ */
+
+static const char *const speed_names[] = {
+ [USB_SPEED_UNKNOWN] = "UNKNOWN",
+ [USB_SPEED_LOW] = "LS",
+ [USB_SPEED_FULL] = "FS",
+ [USB_SPEED_HIGH] = "HS",
+ [USB_SPEED_WIRELESS] = "W",
+ [USB_SPEED_SUPER] = "SS",
+ [USB_SPEED_SUPER_PLUS] = "SS+",
+};
+
+static int get_max_srate(const int *srates)
+{
+ int i, max_srate = 0;
+
+ for (i = 0; i < UAC_MAX_RATES; i++) {
+ if (srates[i] == 0)
+ break;
+ if (srates[i] > max_srate)
+ max_srate = srates[i];
+ }
+ return max_srate;
+}
+
+static int get_max_bw_for_bint(u8 bint, unsigned int factor, int chmask,
+ int srate, int ssize, int sync, int fb_max)
+{
+ u16 max_size_bw;
+
+ if (sync == USB_ENDPOINT_SYNC_ASYNC) {
+ // playback is always async, capture only when configured
+ // Win10 requires max packet size + 1 frame
+ srate = srate * (1000 + fb_max) / 1000;
+ // updated srate is always bigger, therefore DIV_ROUND_UP always yields +1
+ max_size_bw = num_channels(chmask) * ssize *
+ (DIV_ROUND_UP(srate, factor / (1 << (bint - 1))));
+ } else {
+ // adding 1 frame provision for Win10
+ max_size_bw = num_channels(chmask) * ssize *
+ (DIV_ROUND_UP(srate, factor / (1 << (bint - 1))) + 1);
+ }
+ return max_size_bw;
+}
+
+static int uac_set_ep_max_packet_size_bint(struct device *dev,
+ struct usb_endpoint_descriptor *ep_desc,
+ enum usb_device_speed speed, bool is_playback, int hs_bint,
+ int chmask, int srate, int ssize, int sync, int fb_max)
+{
+ u16 max_size_bw, max_size_ep;
+ u8 bint;
+ char *dir;
+
+ switch (speed) {
+ case USB_SPEED_FULL:
+ max_size_ep = 1023;
+ // fixed
+ bint = ep_desc->bInterval;
+ max_size_bw = get_max_bw_for_bint(bint, 1000, chmask, srate,
+ ssize, sync, fb_max);
+ break;
+
+ case USB_SPEED_HIGH:
+ case USB_SPEED_SUPER:
+ max_size_ep = 1024;
+ if (hs_bint > 0) {
+ /* fixed bint */
+ bint = hs_bint;
+ max_size_bw = get_max_bw_for_bint(bint, 8000, chmask, srate,
+ ssize, sync, fb_max);
+ } else {
+ /* checking bInterval from 4 to 1 whether the required bandwidth fits */
+ for (bint = 4; bint > 0; --bint) {
+ max_size_bw = get_max_bw_for_bint(
+ bint, 8000, chmask, srate,
+ ssize, sync, fb_max);
+ if (max_size_bw <= max_size_ep)
+ break;
+ }
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (is_playback)
+ dir = "Playback";
+ else
+ dir = "Capture";
+
+ if (max_size_bw <= max_size_ep)
+ dev_dbg(dev,
+ "%s %s: Would use wMaxPacketSize %d and bInterval %d\n",
+ speed_names[speed], dir, max_size_bw, bint);
+ else {
+ dev_warn(dev,
+ "%s %s: Req. wMaxPacketSize %d at bInterval %d > max ISOC %d, may drop data!\n",
+ speed_names[speed], dir, max_size_bw, bint, max_size_ep);
+ max_size_bw = max_size_ep;
+ }
+
+ ep_desc->wMaxPacketSize = cpu_to_le16(max_size_bw);
+ ep_desc->bInterval = bint;
+
+ return 0;
+}
+
#endif /* __U_UAC_UTILS_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH RFC 05/14] usb: gadget: f_uac1: Add hs_bint to configfs.
2024-09-28 15:08 [PATCH RFC 00/14] usb: gadget: f_uac: Add support for alt mode settings crwulff
` (3 preceding siblings ...)
2024-09-28 15:08 ` [PATCH RFC 04/14] usb: gadget: f_uac2: Move max packet size code to a common header crwulff
@ 2024-09-28 15:08 ` crwulff
2024-09-28 15:08 ` [PATCH RFC 06/14] usb: gadget: f_uac1: Add terminal type attributes crwulff
` (8 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: crwulff @ 2024-09-28 15:08 UTC (permalink / raw)
To: linux-usb
Cc: Pavel Hofman, Greg Kroah-Hartman, James Gruber, Jeff Johnson,
John Keeping, Jonathan Corbet, Lee Jones, Perr Zhang, linux-doc,
linux-kernel, Chris Wulff
From: Chris Wulff <crwulff@gmail.com>
This matches options from f_uac2. This only adds the options but using
it requires additional descriptors added later.
Signed-off-by: Chris Wulff <crwulff@gmail.com>
---
.../ABI/testing/configfs-usb-gadget-uac1 | 2 +
Documentation/usb/gadget-testing.rst | 2 +
drivers/usb/gadget/function/f_uac1.c | 89 +++++++++++++++++++
drivers/usb/gadget/function/u_uac1.h | 4 +
4 files changed, 97 insertions(+)
diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac1 b/Documentation/ABI/testing/configfs-usb-gadget-uac1
index 758b8c9a988a..fed8567b10ec 100644
--- a/Documentation/ABI/testing/configfs-usb-gadget-uac1
+++ b/Documentation/ABI/testing/configfs-usb-gadget-uac1
@@ -8,6 +8,7 @@ Description:
c_chmask capture channel mask
c_srate list of capture sampling rates (comma-separated)
c_ssize capture sample size (bytes)
+ c_hs_bint capture bInterval for HS/SS (1-4: fixed, 0: auto)
c_sync capture synchronization type
(async/adaptive)
c_mute_present capture mute control enable
@@ -22,6 +23,7 @@ Description:
p_chmask playback channel mask
p_srate list of playback sampling rates (comma-separated)
p_ssize playback sample size (bytes)
+ p_hs_bint playback bInterval for HS/SS (1-4: fixed, 0: auto)
p_mute_present playback mute control enable
p_volume_present playback volume control enable
p_volume_min playback volume control min value
diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst
index 68fc0011b388..bdb82b58b260 100644
--- a/Documentation/usb/gadget-testing.rst
+++ b/Documentation/usb/gadget-testing.rst
@@ -958,6 +958,7 @@ The uac1 function provides these attributes in its function directory:
c_volume_min capture volume control min value (in 1/256 dB)
c_volume_max capture volume control max value (in 1/256 dB)
c_volume_res capture volume control resolution (in 1/256 dB)
+ c_hs_bint capture bInterval for HS/SS (1-4: fixed, 0: auto)
fb_max maximum extra bandwidth in async mode
p_chmask playback channel mask
p_srate list of playback sampling rates (comma-separated)
@@ -967,6 +968,7 @@ The uac1 function provides these attributes in its function directory:
p_volume_min playback volume control min value (in 1/256 dB)
p_volume_max playback volume control max value (in 1/256 dB)
p_volume_res playback volume control resolution (in 1/256 dB)
+ p_hs_bint playback bInterval for HS/SS (1-4: fixed, 0: auto)
req_number the number of pre-allocated requests for both capture
and playback
function_name name of the interface
diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c
index 84423d9a8bd7..861e6219552e 100644
--- a/drivers/usb/gadget/function/f_uac1.c
+++ b/drivers/usb/gadget/function/f_uac1.c
@@ -1159,6 +1159,32 @@ f_audio_suspend(struct usb_function *f)
}
/*-------------------------------------------------------------------------*/
+
+static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac1_opts *opts,
+ struct usb_endpoint_descriptor *ep_desc,
+ enum usb_device_speed speed, bool is_playback)
+{
+ int chmask, srate, ssize, hs_bint, sync;
+
+ if (is_playback) {
+ chmask = opts->p_chmask;
+ srate = get_max_srate(opts->p_srates);
+ ssize = opts->p_ssize;
+ hs_bint = opts->p_hs_bint;
+ sync = USB_ENDPOINT_SYNC_ASYNC;
+ } else {
+ chmask = opts->c_chmask;
+ srate = get_max_srate(opts->c_srates);
+ ssize = opts->c_ssize;
+ hs_bint = opts->c_hs_bint;
+ sync = opts->c_sync;
+ }
+
+ return uac_set_ep_max_packet_size_bint(
+ dev, ep_desc, speed, is_playback, hs_bint, chmask,
+ srate, ssize, sync, opts->fb_max);
+}
+
static struct uac_feature_unit_descriptor *build_fu_desc(int chmask)
{
struct uac_feature_unit_descriptor *fu_desc;
@@ -1419,6 +1445,15 @@ static int f_audio_validate_opts(struct g_audio *audio, struct device *dev)
return -EINVAL;
}
+ if ((opts->p_hs_bint < 0) || (opts->p_hs_bint > 4)) {
+ dev_err(dev, "Error: incorrect playback HS/SS bInterval (1-4: fixed, 0: auto)\n");
+ return -EINVAL;
+ }
+ if ((opts->c_hs_bint < 0) || (opts->c_hs_bint > 4)) {
+ dev_err(dev, "Error: incorrect capture HS/SS bInterval (1-4: fixed, 0: auto)\n");
+ return -EINVAL;
+ }
+
return 0;
}
@@ -1613,6 +1648,54 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
uac1->as_in_alt = 0;
}
+ hs_as_in_ep_desc.bInterval = audio_opts->p_hs_bint;
+ ss_as_in_ep_desc.bInterval = audio_opts->p_hs_bint;
+ hs_as_out_ep_desc.bInterval = audio_opts->c_hs_bint;
+ ss_as_out_ep_desc.bInterval = audio_opts->c_hs_bint;
+
+ /* Calculate wMaxPacketSize according to audio bandwidth */
+ status = set_ep_max_packet_size_bint(dev, audio_opts, &fs_as_in_ep_desc,
+ USB_SPEED_FULL, true);
+ if (status < 0) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ goto err_free_fu;
+ }
+
+ status = set_ep_max_packet_size_bint(dev, audio_opts, &fs_as_out_ep_desc,
+ USB_SPEED_FULL, false);
+ if (status < 0) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ goto err_free_fu;
+ }
+
+ status = set_ep_max_packet_size_bint(dev, audio_opts, &hs_as_in_ep_desc,
+ USB_SPEED_HIGH, true);
+ if (status < 0) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ goto err_free_fu;
+ }
+
+ status = set_ep_max_packet_size_bint(dev, audio_opts, &hs_as_out_ep_desc,
+ USB_SPEED_HIGH, false);
+ if (status < 0) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ goto err_free_fu;
+ }
+
+ status = set_ep_max_packet_size_bint(dev, audio_opts, &ss_as_in_ep_desc,
+ USB_SPEED_SUPER, true);
+ if (status < 0) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ goto err_free_fu;
+ }
+
+ status = set_ep_max_packet_size_bint(dev, audio_opts, &hs_as_out_ep_desc,
+ USB_SPEED_SUPER, false);
+ if (status < 0) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ goto err_free_fu;
+ }
+
audio->gadget = gadget;
status = -ENODEV;
@@ -1782,9 +1865,11 @@ UAC1_ATTRIBUTE(u32, c_chmask);
UAC1_RATE_ATTRIBUTE(c_srate);
UAC1_ATTRIBUTE_SYNC(c_sync);
UAC1_ATTRIBUTE(u32, c_ssize);
+UAC1_ATTRIBUTE(u8, c_hs_bint);
UAC1_ATTRIBUTE(u32, p_chmask);
UAC1_RATE_ATTRIBUTE(p_srate);
UAC1_ATTRIBUTE(u32, p_ssize);
+UAC1_ATTRIBUTE(u8, p_hs_bint);
UAC1_ATTRIBUTE(u32, req_number);
UAC1_ATTRIBUTE(bool, p_mute_present);
@@ -1818,9 +1903,11 @@ static struct configfs_attribute *f_uac1_attrs[] = {
&f_uac1_opts_attr_c_srate,
&f_uac1_opts_attr_c_sync,
&f_uac1_opts_attr_c_ssize,
+ &f_uac1_opts_attr_c_hs_bint,
&f_uac1_opts_attr_p_chmask,
&f_uac1_opts_attr_p_srate,
&f_uac1_opts_attr_p_ssize,
+ &f_uac1_opts_attr_p_hs_bint,
&f_uac1_opts_attr_req_number,
&f_uac1_opts_attr_fb_max,
@@ -1883,9 +1970,11 @@ static struct usb_function_instance *f_audio_alloc_inst(void)
opts->c_srates[0] = UAC1_DEF_CSRATE;
opts->c_sync = UAC1_DEF_CSYNC;
opts->c_ssize = UAC1_DEF_CSSIZE;
+ opts->c_hs_bint = UAC1_DEF_CHSBINT;
opts->p_chmask = UAC1_DEF_PCHMASK;
opts->p_srates[0] = UAC1_DEF_PSRATE;
opts->p_ssize = UAC1_DEF_PSSIZE;
+ opts->p_hs_bint = UAC1_DEF_PHSBINT;
opts->p_mute_present = UAC1_DEF_MUTE_PRESENT;
opts->p_volume_present = UAC1_DEF_VOLUME_PRESENT;
diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h
index 59eac5ca8114..c7e7480bf71f 100644
--- a/drivers/usb/gadget/function/u_uac1.h
+++ b/drivers/usb/gadget/function/u_uac1.h
@@ -16,9 +16,11 @@
#define UAC1_DEF_CSRATE 48000
#define UAC1_DEF_CSYNC USB_ENDPOINT_SYNC_ADAPTIVE
#define UAC1_DEF_CSSIZE 2
+#define UAC1_DEF_CHSBINT 0
#define UAC1_DEF_PCHMASK 0x3
#define UAC1_DEF_PSRATE 48000
#define UAC1_DEF_PSSIZE 2
+#define UAC1_DEF_PHSBINT 0
#define UAC1_DEF_REQ_NUM 2
#define UAC1_DEF_INT_REQ_NUM 10
@@ -35,9 +37,11 @@ struct f_uac1_opts {
int c_srates[UAC_MAX_RATES];
int c_sync;
int c_ssize;
+ u8 c_hs_bint;
int p_chmask;
int p_srates[UAC_MAX_RATES];
int p_ssize;
+ u8 p_hs_bint;
bool p_mute_present;
bool p_volume_present;
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH RFC 06/14] usb: gadget: f_uac1: Add terminal type attributes
2024-09-28 15:08 [PATCH RFC 00/14] usb: gadget: f_uac: Add support for alt mode settings crwulff
` (4 preceding siblings ...)
2024-09-28 15:08 ` [PATCH RFC 05/14] usb: gadget: f_uac1: Add hs_bint to configfs crwulff
@ 2024-09-28 15:08 ` crwulff
2024-09-28 15:08 ` [PATCH RFC 07/14] usb: gadget: f_uac1: Add alt mode settings interface crwulff
` (7 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: crwulff @ 2024-09-28 15:08 UTC (permalink / raw)
To: linux-usb
Cc: Pavel Hofman, Greg Kroah-Hartman, James Gruber, Jeff Johnson,
John Keeping, Jonathan Corbet, Lee Jones, Perr Zhang, linux-doc,
linux-kernel, Chris Wulff
From: Chris Wulff <crwulff@gmail.com>
Allow the user to set the terminal types. This matches the options
in f_uac2.
Signed-off-by: Chris Wulff <crwulff@gmail.com>
---
Documentation/ABI/testing/configfs-usb-gadget-uac1 | 2 ++
Documentation/usb/gadget-testing.rst | 2 ++
drivers/usb/gadget/function/f_uac1.c | 11 +++++++++++
drivers/usb/gadget/function/u_uac1.h | 5 +++++
4 files changed, 20 insertions(+)
diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac1 b/Documentation/ABI/testing/configfs-usb-gadget-uac1
index fed8567b10ec..9f7f626329e7 100644
--- a/Documentation/ABI/testing/configfs-usb-gadget-uac1
+++ b/Documentation/ABI/testing/configfs-usb-gadget-uac1
@@ -43,4 +43,6 @@ Description:
c_it_ch_name capture channels name
c_ot_name capture output terminal name
c_fu_vol_name capture mute/volume functional unit name
+ c_terminal_type code of the capture terminal type
+ p_terminal_type code of the playback terminal type
===================== =======================================
diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst
index bdb82b58b260..53c2518dbf50 100644
--- a/Documentation/usb/gadget-testing.rst
+++ b/Documentation/usb/gadget-testing.rst
@@ -980,6 +980,8 @@ The uac1 function provides these attributes in its function directory:
c_it_ch_name capture channels name
c_ot_name capture output terminal name
c_fu_vol_name capture mute/volume functional unit name
+ c_terminal_type code of the capture terminal type
+ p_terminal_type code of the playback terminal type
================ ====================================================
The attributes have sane default values.
diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c
index 861e6219552e..5813fe3e7146 100644
--- a/drivers/usb/gadget/function/f_uac1.c
+++ b/drivers/usb/gadget/function/f_uac1.c
@@ -1277,6 +1277,8 @@ static void setup_descriptor(struct f_uac1_opts *opts)
as_out_header_desc.bTerminalLink = usb_out_it_desc.bTerminalID;
as_in_header_desc.bTerminalLink = usb_in_ot_desc.bTerminalID;
+ io_in_it_desc.wTerminalType = cpu_to_le16(opts->c_terminal_type);
+ io_out_ot_desc.wTerminalType = cpu_to_le16(opts->p_terminal_type);
ac_header_desc->wTotalLength = cpu_to_le16(ac_header_desc->bLength);
if (EPIN_EN(opts)) {
@@ -1898,6 +1900,9 @@ UAC1_ATTRIBUTE_STRING(c_it_ch_name);
UAC1_ATTRIBUTE_STRING(c_ot_name);
UAC1_ATTRIBUTE_STRING(c_fu_vol_name);
+UAC1_ATTRIBUTE(s16, p_terminal_type);
+UAC1_ATTRIBUTE(s16, c_terminal_type);
+
static struct configfs_attribute *f_uac1_attrs[] = {
&f_uac1_opts_attr_c_chmask,
&f_uac1_opts_attr_c_srate,
@@ -1935,6 +1940,9 @@ static struct configfs_attribute *f_uac1_attrs[] = {
&f_uac1_opts_attr_c_ot_name,
&f_uac1_opts_attr_c_fu_vol_name,
+ &f_uac1_opts_attr_p_terminal_type,
+ &f_uac1_opts_attr_c_terminal_type,
+
NULL,
};
@@ -2003,6 +2011,9 @@ static struct usb_function_instance *f_audio_alloc_inst(void)
scnprintf(opts->c_ot_name, sizeof(opts->c_ot_name), "Playback Output terminal");
scnprintf(opts->c_fu_vol_name, sizeof(opts->c_fu_vol_name), "Playback Volume");
+ opts->p_terminal_type = UAC1_DEF_P_TERM_TYPE;
+ opts->c_terminal_type = UAC1_DEF_C_TERM_TYPE;
+
return &opts->func_inst;
}
diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h
index c7e7480bf71f..df29018096d3 100644
--- a/drivers/usb/gadget/function/u_uac1.h
+++ b/drivers/usb/gadget/function/u_uac1.h
@@ -30,6 +30,8 @@
#define UAC1_DEF_MAX_DB 0 /* 0 dB */
#define UAC1_DEF_RES_DB (1*256) /* 1 dB */
+#define UAC1_DEF_P_TERM_TYPE UAC_INPUT_TERMINAL_MICROPHONE
+#define UAC1_DEF_C_TERM_TYPE UAC_OUTPUT_TERMINAL_SPEAKER
struct f_uac1_opts {
struct usb_function_instance func_inst;
@@ -71,6 +73,9 @@ struct f_uac1_opts {
char c_ot_name[USB_MAX_STRING_LEN];
char c_fu_vol_name[USB_MAX_STRING_LEN];
+ s16 p_terminal_type;
+ s16 c_terminal_type;
+
struct mutex lock;
int refcnt;
};
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH RFC 07/14] usb: gadget: f_uac1: Add alt mode settings interface
2024-09-28 15:08 [PATCH RFC 00/14] usb: gadget: f_uac: Add support for alt mode settings crwulff
` (5 preceding siblings ...)
2024-09-28 15:08 ` [PATCH RFC 06/14] usb: gadget: f_uac1: Add terminal type attributes crwulff
@ 2024-09-28 15:08 ` crwulff
2024-09-28 15:08 ` [PATCH RFC 08/14] usb: gadget: f_uac2: " crwulff
` (6 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: crwulff @ 2024-09-28 15:08 UTC (permalink / raw)
To: linux-usb
Cc: Pavel Hofman, Greg Kroah-Hartman, James Gruber, Jeff Johnson,
John Keeping, Jonathan Corbet, Lee Jones, Perr Zhang, linux-doc,
linux-kernel, Chris Wulff
From: Chris Wulff <crwulff@gmail.com>
Add the ability to create c_alt.x and p_alt.x directories to
have different configurations for channels/sample size/sync mode. This
patch only implements the user interface and does not yet alter the
behavior of the function.
Initial values for the alt mode settings are copied from the main settings
at the time the alt mode directory is created.
Signed-off-by: Chris Wulff <crwulff@gmail.com>
---
Documentation/usb/gadget-testing.rst | 19 ++
drivers/usb/gadget/function/f_uac1.c | 311 ++++++++++++++++++++++++---
drivers/usb/gadget/function/u_uac1.h | 79 +++++--
3 files changed, 367 insertions(+), 42 deletions(-)
diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst
index 53c2518dbf50..5aaf03cf8ebf 100644
--- a/Documentation/usb/gadget-testing.rst
+++ b/Documentation/usb/gadget-testing.rst
@@ -982,10 +982,29 @@ The uac1 function provides these attributes in its function directory:
c_fu_vol_name capture mute/volume functional unit name
c_terminal_type code of the capture terminal type
p_terminal_type code of the playback terminal type
+ c_alt.x/ alternate capture setting x (0-255)
+ p_alt.x/ alternate playback setting x (0-255)
================ ====================================================
The attributes have sane default values.
+Alternate settings have these attributes settable. Defaults are copied
+from the associated function-wide settings. Alternate setting 0 only
+has a name and no other settings. If p/c_alt.1 doesn't exist
+function-wide settings will be used for alternate setting 1.
+
+ ================ ====================================================
+ name alternate setting name
+ chmask channel mask
+ ssize sample size (bytes)
+ sync synchronization type (async/adaptive) *capture only*
+ hs_bint bInterval for HS/SS (1-4: fixed, 0: auto)
+ it_name input terminal name
+ it_ch_name first input channel name
+ ot_name output terminal name
+ fu_vol_name mute/volume functional unit name
+ ================ ====================================================
+
Testing the UAC1 function
-------------------------
diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c
index 5813fe3e7146..e7bfc32387bf 100644
--- a/drivers/usb/gadget/function/f_uac1.c
+++ b/drivers/usb/gadget/function/f_uac1.c
@@ -21,6 +21,9 @@
#include "u_uac1.h"
#include "u_uac_utils.h"
+#define HOST_TO_DEVICE 0
+#define DEVICE_TO_HOST 1
+
/* UAC1 spec: 3.7.2.3 Audio Channel Cluster Format */
#define UAC1_CHANNEL_MASK 0x0FFF
@@ -1160,29 +1163,14 @@ f_audio_suspend(struct usb_function *f)
/*-------------------------------------------------------------------------*/
-static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac1_opts *opts,
- struct usb_endpoint_descriptor *ep_desc,
- enum usb_device_speed speed, bool is_playback)
+static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac1_alt_opts *alt_opts,
+ struct usb_endpoint_descriptor *ep_desc,
+ enum usb_device_speed speed, bool is_playback)
{
- int chmask, srate, ssize, hs_bint, sync;
-
- if (is_playback) {
- chmask = opts->p_chmask;
- srate = get_max_srate(opts->p_srates);
- ssize = opts->p_ssize;
- hs_bint = opts->p_hs_bint;
- sync = USB_ENDPOINT_SYNC_ASYNC;
- } else {
- chmask = opts->c_chmask;
- srate = get_max_srate(opts->c_srates);
- ssize = opts->c_ssize;
- hs_bint = opts->c_hs_bint;
- sync = opts->c_sync;
- }
-
return uac_set_ep_max_packet_size_bint(
- dev, ep_desc, speed, is_playback, hs_bint, chmask,
- srate, ssize, sync, opts->fb_max);
+ dev, ep_desc, speed, is_playback, alt_opts->hs_bint,
+ alt_opts->chmask, get_max_srate(alt_opts->srates),
+ alt_opts->ssize, alt_opts->sync, alt_opts->c.opts->fb_max);
}
static struct uac_feature_unit_descriptor *build_fu_desc(int chmask)
@@ -1459,6 +1447,52 @@ static int f_audio_validate_opts(struct g_audio *audio, struct device *dev)
return 0;
}
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Configfs alt mode handling
+ */
+
+static void init_alt_0_opts(struct f_uac1_alt_0_opts *alt_0_opts,
+ struct f_uac1_opts *opts, int playback)
+{
+ alt_0_opts->c.opts = opts;
+ alt_0_opts->c.alt_num = 0;
+
+ // Note: Strings are from the host perspective, opt prefixes are from the device perspective
+ scnprintf(alt_0_opts->name, sizeof(alt_0_opts->name),
+ (!playback) ? "Playback Inactive" : "Capture Inactive");
+}
+
+static void init_alt_opts(struct f_uac1_alt_opts *alt_opts, struct f_uac1_opts *opts,
+ int alt_num, int playback)
+{
+ alt_opts->c.opts = opts;
+ alt_opts->c.alt_num = alt_num;
+
+ INIT_LIST_HEAD(&alt_opts->list);
+
+ // Note: Strings are from the host perspective, opt prefixes are from the device perspective
+ scnprintf(alt_opts->name, sizeof(alt_opts->name),
+ (!playback) ? "Playback Active" : "Capture Active");
+ strscpy(alt_opts->it_name, (playback) ? opts->p_it_name : opts->c_it_name,
+ sizeof(alt_opts->it_name));
+ strscpy(alt_opts->it_ch_name, (playback) ? opts->p_it_ch_name : opts->c_it_ch_name,
+ sizeof(alt_opts->it_ch_name));
+ strscpy(alt_opts->ot_name, (playback) ? opts->p_ot_name : opts->c_ot_name,
+ sizeof(alt_opts->ot_name));
+ strscpy(alt_opts->fu_vol_name, (playback) ? opts->p_fu_vol_name : opts->c_fu_vol_name,
+ sizeof(alt_opts->fu_vol_name));
+
+ /* Copy default options from the main opts */
+ alt_opts->chmask = (playback) ? opts->p_chmask : opts->c_chmask;
+ alt_opts->ssize = (playback) ? opts->p_ssize : opts->c_ssize;
+ alt_opts->hs_bint = (playback) ? opts->p_hs_bint : opts->c_hs_bint;
+ alt_opts->srates = (playback) ? opts->p_srates : opts->c_srates;
+ alt_opts->sync = (playback) ? 0 : opts->c_sync;
+ alt_opts->terminal_type = (playback) ? opts->p_terminal_type : opts->c_terminal_type;
+}
+
/* audio function driver setup/binding */
static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
{
@@ -1474,12 +1508,24 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
int status;
int idx, i;
+ audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst);
+
+ /* Copy main options to alt modes 0/1 if the groups don't exist
+ * before validation since they will be checked.
+ */
+ if (!audio_opts->c_alt_0_opts.c.group.cg_item.ci_name)
+ init_alt_0_opts(&audio_opts->c_alt_0_opts, audio_opts, HOST_TO_DEVICE);
+ if (!audio_opts->p_alt_0_opts.c.group.cg_item.ci_name)
+ init_alt_0_opts(&audio_opts->p_alt_0_opts, audio_opts, DEVICE_TO_HOST);
+ if (!audio_opts->c_alt_1_opts.c.group.cg_item.ci_name)
+ init_alt_opts(&audio_opts->c_alt_1_opts, audio_opts, 1, HOST_TO_DEVICE);
+ if (!audio_opts->p_alt_1_opts.c.group.cg_item.ci_name)
+ init_alt_opts(&audio_opts->p_alt_1_opts, audio_opts, 1, DEVICE_TO_HOST);
+
status = f_audio_validate_opts(audio, dev);
if (status)
return status;
- audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst);
-
strings_uac1[STR_AC_IF].s = audio_opts->function_name;
strings_uac1[STR_USB_OUT_IT].s = audio_opts->c_it_name;
@@ -1656,42 +1702,42 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
ss_as_out_ep_desc.bInterval = audio_opts->c_hs_bint;
/* Calculate wMaxPacketSize according to audio bandwidth */
- status = set_ep_max_packet_size_bint(dev, audio_opts, &fs_as_in_ep_desc,
+ status = set_ep_max_packet_size_bint(dev, &audio_opts->p_alt_1_opts, &fs_as_in_ep_desc,
USB_SPEED_FULL, true);
if (status < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
goto err_free_fu;
}
- status = set_ep_max_packet_size_bint(dev, audio_opts, &fs_as_out_ep_desc,
+ status = set_ep_max_packet_size_bint(dev, &audio_opts->c_alt_1_opts, &fs_as_out_ep_desc,
USB_SPEED_FULL, false);
if (status < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
goto err_free_fu;
}
- status = set_ep_max_packet_size_bint(dev, audio_opts, &hs_as_in_ep_desc,
+ status = set_ep_max_packet_size_bint(dev, &audio_opts->p_alt_1_opts, &hs_as_in_ep_desc,
USB_SPEED_HIGH, true);
if (status < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
goto err_free_fu;
}
- status = set_ep_max_packet_size_bint(dev, audio_opts, &hs_as_out_ep_desc,
+ status = set_ep_max_packet_size_bint(dev, &audio_opts->c_alt_1_opts, &hs_as_out_ep_desc,
USB_SPEED_HIGH, false);
if (status < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
goto err_free_fu;
}
- status = set_ep_max_packet_size_bint(dev, audio_opts, &ss_as_in_ep_desc,
+ status = set_ep_max_packet_size_bint(dev, &audio_opts->p_alt_1_opts, &ss_as_in_ep_desc,
USB_SPEED_SUPER, true);
if (status < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
goto err_free_fu;
}
- status = set_ep_max_packet_size_bint(dev, audio_opts, &hs_as_out_ep_desc,
+ status = set_ep_max_packet_size_bint(dev, &audio_opts->c_alt_1_opts, &hs_as_out_ep_desc,
USB_SPEED_SUPER, false);
if (status < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
@@ -1835,6 +1881,108 @@ static inline struct f_uac1_opts *to_f_uac1_opts(struct config_item *item)
func_inst.group);
}
+static inline struct f_uac1_alt_opts *to_f_uac1_alt_opts(struct config_item *item)
+{
+ return container_of(to_config_group(item), struct f_uac1_alt_opts,
+ c.group);
+}
+
+#define UAC1_ALT_ATTR_TO_OPTS struct f_uac1_alt_opts *alt_opts = to_f_uac1_alt_opts(item)
+#define UAC1_ALT_ATTRIBUTE(type, name) \
+ UAC_ATTRIBUTE(f_uac1_alt_opts, UAC1_ALT_ATTR_TO_OPTS, alt_opts, \
+ alt_opts->c.opts->lock, alt_opts->c.opts->refcnt, \
+ type, name)
+
+#define UAC1_ALT_ATTRIBUTE_SYNC(name) \
+ UAC_ATTRIBUTE_SYNC(f_uac1_alt_opts, UAC1_ALT_ATTR_TO_OPTS, \
+ alt_opts, alt_opts->c.opts->lock, \
+ alt_opts->c.opts->refcnt, name)
+
+#define UAC1_ALT_ATTRIBUTE_STRING(name) \
+ UAC_ATTRIBUTE_STRING(f_uac1_alt_opts, UAC1_ALT_ATTR_TO_OPTS, \
+ alt_opts, alt_opts->c.opts->lock, \
+ alt_opts->c.opts->refcnt, name)
+
+
+UAC1_ALT_ATTRIBUTE_STRING(name);
+UAC1_ALT_ATTRIBUTE_STRING(it_name);
+UAC1_ALT_ATTRIBUTE_STRING(it_ch_name);
+UAC1_ALT_ATTRIBUTE_STRING(ot_name);
+UAC1_ALT_ATTRIBUTE_STRING(fu_vol_name);
+
+UAC1_ALT_ATTRIBUTE(u32, ssize);
+UAC1_ALT_ATTRIBUTE(u8, hs_bint);
+UAC1_ALT_ATTRIBUTE(u32, chmask);
+UAC1_ALT_ATTRIBUTE_SYNC(sync);
+UAC1_ALT_ATTRIBUTE(s16, terminal_type);
+
+static struct configfs_attribute *f_uac1_alt_0_attrs[] = {
+ &f_uac1_alt_opts_attr_name,
+
+ NULL,
+};
+
+static const struct config_item_type alt_mode_0_type = {
+ .ct_attrs = f_uac1_alt_0_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct configfs_attribute *f_uac1_alt_attrs_c[] = {
+ &f_uac1_alt_opts_attr_name,
+ &f_uac1_alt_opts_attr_it_name,
+ &f_uac1_alt_opts_attr_it_ch_name,
+ &f_uac1_alt_opts_attr_ot_name,
+ &f_uac1_alt_opts_attr_fu_vol_name,
+ &f_uac1_alt_opts_attr_ssize,
+ &f_uac1_alt_opts_attr_hs_bint,
+ &f_uac1_alt_opts_attr_chmask,
+ &f_uac1_alt_opts_attr_sync,
+ &f_uac1_alt_opts_attr_terminal_type,
+
+ NULL,
+};
+
+static struct configfs_attribute *f_uac1_alt_attrs_p[] = {
+ &f_uac1_alt_opts_attr_name,
+ &f_uac1_alt_opts_attr_it_name,
+ &f_uac1_alt_opts_attr_it_ch_name,
+ &f_uac1_alt_opts_attr_ot_name,
+ &f_uac1_alt_opts_attr_fu_vol_name,
+ &f_uac1_alt_opts_attr_ssize,
+ &f_uac1_alt_opts_attr_hs_bint,
+ &f_uac1_alt_opts_attr_chmask,
+ &f_uac1_alt_opts_attr_terminal_type,
+
+ NULL,
+};
+
+static void f_uac1_alt_attr_release(struct config_item *item)
+{
+ struct f_uac1_alt_opts *alt_opts = to_f_uac1_alt_opts(item);
+
+ /* Opts 0 and 1 are fixed structures, 2+ are kzalloc'd */
+ if (alt_opts->c.alt_num > 1)
+ kfree(alt_opts);
+}
+
+static struct configfs_item_operations f_uac1_alt_item_ops = {
+ .release = f_uac1_alt_attr_release,
+};
+
+static const struct config_item_type alt_mode_c_type = {
+ .ct_item_ops = &f_uac1_alt_item_ops,
+ .ct_attrs = f_uac1_alt_attrs_c,
+ .ct_owner = THIS_MODULE,
+};
+
+static const struct config_item_type alt_mode_p_type = {
+ .ct_item_ops = &f_uac1_alt_item_ops,
+ .ct_attrs = f_uac1_alt_attrs_p,
+ .ct_owner = THIS_MODULE,
+};
+
+/*-------------------------------------------------------------------------*/
+
static void f_uac1_attr_release(struct config_item *item)
{
struct f_uac1_opts *opts = to_f_uac1_opts(item);
@@ -1946,8 +2094,109 @@ static struct configfs_attribute *f_uac1_attrs[] = {
NULL,
};
+static struct config_group *f_uac1_group_make(
+ struct config_group *group,
+ const char *name)
+{
+ struct f_uac1_opts *opts = to_f_uac1_opts(&group->cg_item);
+ struct f_uac1_alt_opts *alt_opts;
+ struct f_uac1_alt_opts *pos;
+ struct config_group *ret;
+ unsigned int alt_num;
+ int playback = 0;
+
+ mutex_lock(&opts->lock);
+ if (opts->refcnt) {
+ ret = ERR_PTR(-EBUSY);
+ goto end;
+ }
+
+ if (sscanf(name, "c_alt.%u", &alt_num) != 1) {
+ playback = 1;
+ if (sscanf(name, "p_alt.%u", &alt_num) != 1) {
+ ret = ERR_PTR(-EINVAL);
+ goto end;
+ }
+ }
+
+ if (alt_num > 255) {
+ ret = ERR_PTR(-EINVAL);
+ goto end;
+ }
+
+ /* Alt mode 0 has less properties */
+ if (alt_num == 0) {
+ struct f_uac1_alt_0_opts *alt_0_opts = (playback) ? &opts->p_alt_0_opts
+ : &opts->c_alt_0_opts;
+ init_alt_0_opts(alt_0_opts, opts, playback);
+ config_group_init_type_name(&alt_0_opts->c.group, name, &alt_mode_0_type);
+ ret = &alt_0_opts->c.group;
+ goto end;
+ }
+
+ if (alt_num == 1) {
+ /* Alt mode 1 always exists */
+ alt_opts = (playback) ? &opts->p_alt_1_opts : &opts->c_alt_1_opts;
+ } else {
+ /* Allocate a structure for alt mode 2+ */
+ alt_opts = kzalloc(sizeof(*alt_opts), GFP_KERNEL);
+ if (!alt_opts) {
+ ret = ERR_PTR(-ENOMEM);
+ goto end;
+ }
+ }
+
+ ret = &alt_opts->c.group;
+
+ config_group_init_type_name(&alt_opts->c.group, name, (playback) ? &alt_mode_p_type
+ : &alt_mode_c_type);
+
+ init_alt_opts(alt_opts, opts, alt_num, playback);
+
+ /* Alt mode 1 doesn't go in the list. It is handled separately to
+ * also handle the case where the alt.1 group is not created.
+ */
+ if (alt_num == 1)
+ goto end;
+
+ /* Insert the new alt mode in the list, sorted by alt_num */
+ list_for_each_entry(pos, (playback) ? &opts->p_alt_opts : &opts->c_alt_opts, list) {
+ if (alt_opts->c.alt_num < pos->c.alt_num) {
+ list_add_tail(&alt_opts->list, &pos->list);
+ goto end;
+ }
+ }
+
+ list_add_tail(&alt_opts->list, (playback) ? &opts->p_alt_opts : &opts->c_alt_opts);
+
+end:
+ mutex_unlock(&opts->lock);
+
+ return ret;
+}
+
+static void f_uac1_group_drop(struct config_group *group, struct config_item *item)
+{
+ struct f_uac1_alt_opts *alt_opts = to_f_uac1_alt_opts(item);
+
+ /* Alt modes 0 and 1 are preallocated and not included in the list */
+ if (alt_opts->c.alt_num > 1) {
+ mutex_lock(&alt_opts->c.opts->lock);
+ list_del(&alt_opts->list);
+ mutex_unlock(&alt_opts->c.opts->lock);
+ }
+
+ config_item_put(item);
+}
+
+static struct configfs_group_operations f_uac1_group_ops = {
+ .make_group = &f_uac1_group_make,
+ .drop_item = &f_uac1_group_drop,
+};
+
static const struct config_item_type f_uac1_func_type = {
.ct_item_ops = &f_uac1_item_ops,
+ .ct_group_ops = &f_uac1_group_ops,
.ct_attrs = f_uac1_attrs,
.ct_owner = THIS_MODULE,
};
@@ -1971,6 +2220,9 @@ static struct usb_function_instance *f_audio_alloc_inst(void)
mutex_init(&opts->lock);
opts->func_inst.free_func_inst = f_audio_free_inst;
+ INIT_LIST_HEAD(&opts->c_alt_opts);
+ INIT_LIST_HEAD(&opts->p_alt_opts);
+
config_group_init_type_name(&opts->func_inst.group, "",
&f_uac1_func_type);
@@ -2001,6 +2253,7 @@ static struct usb_function_instance *f_audio_alloc_inst(void)
scnprintf(opts->function_name, sizeof(opts->function_name), "AC Interface");
+ // Note: Strings are from the host perspective, opt prefixes are from the device perspective
scnprintf(opts->p_it_name, sizeof(opts->p_it_name), "Capture Input terminal");
scnprintf(opts->p_it_ch_name, sizeof(opts->p_it_ch_name), "Capture Channels");
scnprintf(opts->p_ot_name, sizeof(opts->p_ot_name), "Capture Output terminal");
diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h
index df29018096d3..ae69f1eb872d 100644
--- a/drivers/usb/gadget/function/u_uac1.h
+++ b/drivers/usb/gadget/function/u_uac1.h
@@ -33,8 +33,61 @@
#define UAC1_DEF_P_TERM_TYPE UAC_INPUT_TERMINAL_MICROPHONE
#define UAC1_DEF_C_TERM_TYPE UAC_OUTPUT_TERMINAL_SPEAKER
+
+struct f_uac1_opts;
+
+struct f_uac1_alt_opts_common {
+ struct config_group group;
+ struct f_uac1_opts *opts;
+ u8 alt_num;
+};
+
+/* Alt mode 0 only has a name */
+struct f_uac1_alt_0_opts {
+ struct f_uac1_alt_opts_common c;
+
+ char name[USB_MAX_STRING_LEN];
+};
+
+/* Alt modes 1+ */
+struct f_uac1_alt_opts {
+ struct f_uac1_alt_opts_common c;
+
+ struct list_head list;
+
+ /* Strings */
+ char name[USB_MAX_STRING_LEN];
+ char it_name[USB_MAX_STRING_LEN];
+ char it_ch_name[USB_MAX_STRING_LEN];
+ char ot_name[USB_MAX_STRING_LEN];
+ char fu_vol_name[USB_MAX_STRING_LEN];
+
+ /* Audio options */
+ int chmask;
+ int *srates; /* Reference to p/c_srates in opts */
+ int sync;
+ int ssize;
+ u8 hs_bint;
+ s16 terminal_type;
+
+};
+
struct f_uac1_opts {
struct usb_function_instance func_inst;
+
+ /* Alt mode 0 options */
+ struct f_uac1_alt_0_opts c_alt_0_opts;
+ struct f_uac1_alt_0_opts p_alt_0_opts;
+
+ /* Alt mode 1 options */
+ struct f_uac1_alt_opts c_alt_1_opts;
+ struct f_uac1_alt_opts p_alt_1_opts;
+
+ /* Alt mode 2+ options */
+ struct list_head c_alt_opts;
+ struct list_head p_alt_opts;
+
+ /* Default options and Alt mode 1 if no c/p_alt.1 created */
int c_chmask;
int c_srates[UAC_MAX_RATES];
int c_sync;
@@ -45,14 +98,14 @@ struct f_uac1_opts {
int p_ssize;
u8 p_hs_bint;
- bool p_mute_present;
- bool p_volume_present;
+ bool p_mute_present;
+ bool p_volume_present;
s16 p_volume_min;
s16 p_volume_max;
s16 p_volume_res;
- bool c_mute_present;
- bool c_volume_present;
+ bool c_mute_present;
+ bool c_volume_present;
s16 c_volume_min;
s16 c_volume_max;
s16 c_volume_res;
@@ -61,17 +114,17 @@ struct f_uac1_opts {
int fb_max;
unsigned bound:1;
- char function_name[USB_MAX_STRING_LEN];
+ char function_name[USB_MAX_STRING_LEN];
- char p_it_name[USB_MAX_STRING_LEN];
- char p_it_ch_name[USB_MAX_STRING_LEN];
- char p_ot_name[USB_MAX_STRING_LEN];
- char p_fu_vol_name[USB_MAX_STRING_LEN];
+ char p_it_name[USB_MAX_STRING_LEN];
+ char p_it_ch_name[USB_MAX_STRING_LEN];
+ char p_ot_name[USB_MAX_STRING_LEN];
+ char p_fu_vol_name[USB_MAX_STRING_LEN];
- char c_it_name[USB_MAX_STRING_LEN];
- char c_it_ch_name[USB_MAX_STRING_LEN];
- char c_ot_name[USB_MAX_STRING_LEN];
- char c_fu_vol_name[USB_MAX_STRING_LEN];
+ char c_it_name[USB_MAX_STRING_LEN];
+ char c_it_ch_name[USB_MAX_STRING_LEN];
+ char c_ot_name[USB_MAX_STRING_LEN];
+ char c_fu_vol_name[USB_MAX_STRING_LEN];
s16 p_terminal_type;
s16 c_terminal_type;
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH RFC 08/14] usb: gadget: f_uac2: Add alt mode settings interface
2024-09-28 15:08 [PATCH RFC 00/14] usb: gadget: f_uac: Add support for alt mode settings crwulff
` (6 preceding siblings ...)
2024-09-28 15:08 ` [PATCH RFC 07/14] usb: gadget: f_uac1: Add alt mode settings interface crwulff
@ 2024-09-28 15:08 ` crwulff
2024-09-28 15:09 ` [PATCH RFC 09/14] usb: gadget: f_uac1: Make string table dynamic with strings from all alt modes crwulff
` (5 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: crwulff @ 2024-09-28 15:08 UTC (permalink / raw)
To: linux-usb
Cc: Pavel Hofman, Greg Kroah-Hartman, James Gruber, Jeff Johnson,
John Keeping, Jonathan Corbet, Lee Jones, Perr Zhang, linux-doc,
linux-kernel, Chris Wulff
From: Chris Wulff <crwulff@gmail.com>
Add the ability to create c_alt.x and p_alt.x directories to
have different configurations for channels/sample size/sync mode. This
patch only implements the user interface and does not yet alter the
behavior of the function.
Initial values for the alt mode settings are copied from the main settings
at the time the alt mode directory is created.
Signed-off-by: Chris Wulff <crwulff@gmail.com>
---
Documentation/usb/gadget-testing.rst | 21 +-
drivers/usb/gadget/function/f_uac2.c | 313 ++++++++++++++++++++++++---
drivers/usb/gadget/function/u_uac2.h | 91 ++++++--
3 files changed, 379 insertions(+), 46 deletions(-)
diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst
index 5aaf03cf8ebf..67cae833f246 100644
--- a/Documentation/usb/gadget-testing.rst
+++ b/Documentation/usb/gadget-testing.rst
@@ -762,7 +762,7 @@ The uac2 function provides these attributes in its function directory:
p_volume_max playback volume control max value (in 1/256 dB)
p_volume_res playback volume control resolution (in 1/256 dB)
p_hs_bint playback bInterval for HS/SS (1-4: fixed, 0: auto)
- req_number the number of pre-allocated request for both capture
+ req_number the number of pre-allocated requests for both capture
and playback
function_name name of the interface
if_ctrl_name topology control name
@@ -778,10 +778,29 @@ The uac2 function provides these attributes in its function directory:
c_fu_vol_name capture functional unit name
c_terminal_type code of the capture terminal type
p_terminal_type code of the playback terminal type
+ c_alt.x/ alternate capture setting x (0-255)
+ p_alt.x/ alternate playback setting x (0-255)
================ ====================================================
The attributes have sane default values.
+Alternate settings have these attributes settable. Defaults are copied
+from the associated function-wide settings. Alternate setting 0 only
+has a name and no other settings. If p/c_alt.1 doesn't exist
+function-wide settings will be used for alternate setting 1.
+
+ ================ ====================================================
+ name alternate setting name
+ chmask channel mask
+ ssize sample size (bytes)
+ sync synchronization type (async/adaptive) *capture only*
+ hs_bint bInterval for HS/SS (1-4: fixed, 0: auto)
+ it_name input terminal name
+ it_ch_name first input channel name
+ ot_name output terminal name
+ fu_vol_name mute/volume functional unit name
+ ================ ====================================================
+
Testing the UAC2 function
-------------------------
diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index 050789d2d3c9..e9f951215c26 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -19,6 +19,9 @@
#include "u_uac2.h"
#include "u_uac_utils.h"
+#define HOST_TO_DEVICE 0
+#define DEVICE_TO_HOST 1
+
/* UAC2 spec: 4.1 Audio Channel Cluster Descriptor */
#define UAC2_CHANNEL_MASK 0x07FFFFFF
@@ -646,29 +649,15 @@ struct cntrl_subrange_lay3 {
DECLARE_UAC2_CNTRL_RANGES_LAY3(srates, UAC_MAX_RATES);
-static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac2_opts *uac2_opts,
+static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac2_alt_opts *alt_opts,
struct usb_endpoint_descriptor *ep_desc,
enum usb_device_speed speed, bool is_playback)
{
- int chmask, srate, ssize, hs_bint, sync;
-
- if (is_playback) {
- chmask = uac2_opts->p_chmask;
- srate = get_max_srate(uac2_opts->p_srates);
- ssize = uac2_opts->p_ssize;
- hs_bint = uac2_opts->p_hs_bint;
- sync = USB_ENDPOINT_SYNC_ASYNC;
- } else {
- chmask = uac2_opts->c_chmask;
- srate = get_max_srate(uac2_opts->c_srates);
- ssize = uac2_opts->c_ssize;
- hs_bint = uac2_opts->c_hs_bint;
- sync = uac2_opts->c_sync;
- }
-
return uac_set_ep_max_packet_size_bint(
- dev, ep_desc, speed, is_playback, hs_bint, chmask,
- srate, ssize, sync, uac2_opts->fb_max);
+ dev, ep_desc, speed, is_playback, alt_opts->hs_bint, alt_opts->chmask,
+ get_max_srate(is_playback ? alt_opts->c.opts->p_srates
+ : alt_opts->c.opts->c_srates),
+ alt_opts->ssize, alt_opts->sync, alt_opts->c.opts->fb_max);
}
static struct uac2_feature_unit_descriptor *build_fu_desc(int chmask)
@@ -926,6 +915,57 @@ static int afunc_validate_opts(struct g_audio *agdev, struct device *dev)
return 0;
}
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Configfs alt mode handling
+ */
+
+static void init_alt_0_opts(struct f_uac2_alt_0_opts *alt_0_opts,
+ struct f_uac2_opts *opts, int playback)
+{
+ alt_0_opts->c.opts = opts;
+ alt_0_opts->c.alt_num = 0;
+
+ // Note: Strings are from the host perspective, opt prefixes are from the device perspective
+ scnprintf(alt_0_opts->name, sizeof(alt_0_opts->name),
+ (!playback) ? "Playback Inactive" : "Capture Inactive");
+}
+
+static void init_alt_opts(struct f_uac2_alt_opts *alt_opts, struct f_uac2_opts *opts,
+ int alt_num, int playback)
+{
+ alt_opts->c.opts = opts;
+ alt_opts->c.alt_num = alt_num;
+
+ INIT_LIST_HEAD(&alt_opts->list);
+
+ // Note: Strings are from the host perspective, opt prefixes are from the device perspective
+ scnprintf(alt_opts->name, sizeof(alt_opts->name),
+ (!playback) ? "Playback Active" : "Capture Active");
+ strscpy(alt_opts->it_name, (playback) ? opts->p_it_name : opts->c_it_name,
+ sizeof(alt_opts->it_name));
+ strscpy(alt_opts->it_ch_name, (playback) ? opts->p_it_ch_name : opts->c_it_ch_name,
+ sizeof(alt_opts->it_ch_name));
+ strscpy(alt_opts->ot_name, (playback) ? opts->p_ot_name : opts->c_ot_name,
+ sizeof(alt_opts->ot_name));
+ strscpy(alt_opts->fu_vol_name, (playback) ? opts->p_fu_vol_name : opts->c_fu_vol_name,
+ sizeof(alt_opts->fu_vol_name));
+
+ /* Copy default options from the main opts */
+ alt_opts->chmask = (playback) ? opts->p_chmask : opts->c_chmask;
+ alt_opts->ssize = (playback) ? opts->p_ssize : opts->c_ssize;
+ alt_opts->sync = (playback) ? USB_ENDPOINT_SYNC_ASYNC : opts->c_sync; /* only for capture */
+ alt_opts->hs_bint = (playback) ? opts->p_hs_bint : opts->c_hs_bint;
+
+ /* NOTE: These are backwards with relation to other c_/p_ settings in the existing
+ * userspace API. Correct terminal type is copied into c/p_alt.x
+ * (eg p_termial_type == c_alt.x/terminal_type)
+ */
+ alt_opts->terminal_type = (!playback) ? opts->p_terminal_type : opts->c_terminal_type;
+}
+
+
static int
afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
{
@@ -938,6 +978,18 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
struct usb_string *us;
int ret;
+ /* Copy main options to alt modes 0/1 if the groups don't exist
+ * before validation since they will be checked.
+ */
+ if (!uac2_opts->c_alt_0_opts.c.group.cg_item.ci_name)
+ init_alt_0_opts(&uac2_opts->c_alt_0_opts, uac2_opts, HOST_TO_DEVICE);
+ if (!uac2_opts->p_alt_0_opts.c.group.cg_item.ci_name)
+ init_alt_0_opts(&uac2_opts->p_alt_0_opts, uac2_opts, DEVICE_TO_HOST);
+ if (!uac2_opts->c_alt_1_opts.c.group.cg_item.ci_name)
+ init_alt_opts(&uac2_opts->c_alt_1_opts, uac2_opts, 1, HOST_TO_DEVICE);
+ if (!uac2_opts->p_alt_1_opts.c.group.cg_item.ci_name)
+ init_alt_opts(&uac2_opts->p_alt_1_opts, uac2_opts, 1, DEVICE_TO_HOST);
+
ret = afunc_validate_opts(agdev, dev);
if (ret)
return ret;
@@ -1109,42 +1161,42 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
ss_epout_desc.bInterval = uac2_opts->c_hs_bint;
/* Calculate wMaxPacketSize according to audio bandwidth */
- ret = set_ep_max_packet_size_bint(dev, uac2_opts, &fs_epin_desc,
+ ret = set_ep_max_packet_size_bint(dev, &uac2_opts->p_alt_1_opts, &fs_epin_desc,
USB_SPEED_FULL, true);
if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
return ret;
}
- ret = set_ep_max_packet_size_bint(dev, uac2_opts, &fs_epout_desc,
+ ret = set_ep_max_packet_size_bint(dev, &uac2_opts->c_alt_1_opts, &fs_epout_desc,
USB_SPEED_FULL, false);
if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
return ret;
}
- ret = set_ep_max_packet_size_bint(dev, uac2_opts, &hs_epin_desc,
+ ret = set_ep_max_packet_size_bint(dev, &uac2_opts->p_alt_1_opts, &hs_epin_desc,
USB_SPEED_HIGH, true);
if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
return ret;
}
- ret = set_ep_max_packet_size_bint(dev, uac2_opts, &hs_epout_desc,
+ ret = set_ep_max_packet_size_bint(dev, &uac2_opts->c_alt_1_opts, &hs_epout_desc,
USB_SPEED_HIGH, false);
if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
return ret;
}
- ret = set_ep_max_packet_size_bint(dev, uac2_opts, &ss_epin_desc,
+ ret = set_ep_max_packet_size_bint(dev, &uac2_opts->p_alt_1_opts, &ss_epin_desc,
USB_SPEED_SUPER, true);
if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
return ret;
}
- ret = set_ep_max_packet_size_bint(dev, uac2_opts, &ss_epout_desc,
+ ret = set_ep_max_packet_size_bint(dev, &uac2_opts->c_alt_1_opts, &ss_epout_desc,
USB_SPEED_SUPER, false);
if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
@@ -1773,6 +1825,110 @@ static inline struct f_uac2_opts *to_f_uac2_opts(struct config_item *item)
func_inst.group);
}
+static inline struct f_uac2_alt_opts *to_f_uac2_alt_opts(struct config_item *item)
+{
+ return container_of(to_config_group(item), struct f_uac2_alt_opts,
+ c.group);
+}
+
+#define UAC2_ALT_ATTR_TO_OPTS struct f_uac2_alt_opts *alt_opts = to_f_uac2_alt_opts(item)
+#define UAC2_ALT_ATTRIBUTE(type, name) \
+ UAC_ATTRIBUTE(f_uac2_alt_opts, UAC2_ALT_ATTR_TO_OPTS, alt_opts, \
+ alt_opts->c.opts->lock, alt_opts->c.opts->refcnt, \
+ type, name)
+
+#define UAC2_ALT_ATTRIBUTE_SYNC(name) \
+ UAC_ATTRIBUTE_SYNC(f_uac2_alt_opts, UAC2_ALT_ATTR_TO_OPTS, \
+ alt_opts, alt_opts->c.opts->lock, \
+ alt_opts->c.opts->refcnt, name)
+
+#define UAC2_ALT_ATTRIBUTE_STRING(name) \
+ UAC_ATTRIBUTE_STRING(f_uac2_alt_opts, UAC2_ALT_ATTR_TO_OPTS, \
+ alt_opts, alt_opts->c.opts->lock, \
+ alt_opts->c.opts->refcnt, name)
+
+
+UAC2_ALT_ATTRIBUTE_STRING(name);
+UAC2_ALT_ATTRIBUTE_STRING(it_name);
+UAC2_ALT_ATTRIBUTE_STRING(it_ch_name);
+UAC2_ALT_ATTRIBUTE_STRING(ot_name);
+UAC2_ALT_ATTRIBUTE_STRING(fu_vol_name);
+
+UAC2_ALT_ATTRIBUTE(u32, ssize);
+UAC2_ALT_ATTRIBUTE(u32, chmask);
+UAC2_ALT_ATTRIBUTE_SYNC(sync);
+UAC2_ALT_ATTRIBUTE(u8, hs_bint);
+UAC2_ALT_ATTRIBUTE(s16, terminal_type);
+
+static struct configfs_attribute *f_uac2_alt_0_attrs[] = {
+ &f_uac2_alt_opts_attr_name,
+
+ NULL,
+};
+
+static const struct config_item_type alt_mode_0_type = {
+ .ct_attrs = f_uac2_alt_0_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct configfs_attribute *f_uac2_alt_attrs_c[] = {
+ &f_uac2_alt_opts_attr_name,
+ &f_uac2_alt_opts_attr_it_name,
+ &f_uac2_alt_opts_attr_it_ch_name,
+ &f_uac2_alt_opts_attr_ot_name,
+ &f_uac2_alt_opts_attr_fu_vol_name,
+ &f_uac2_alt_opts_attr_ssize,
+ &f_uac2_alt_opts_attr_chmask,
+ &f_uac2_alt_opts_attr_sync,
+ &f_uac2_alt_opts_attr_hs_bint,
+ &f_uac2_alt_opts_attr_terminal_type,
+
+ NULL,
+};
+
+static struct configfs_attribute *f_uac2_alt_attrs_p[] = {
+ &f_uac2_alt_opts_attr_name,
+ &f_uac2_alt_opts_attr_it_name,
+ &f_uac2_alt_opts_attr_it_ch_name,
+ &f_uac2_alt_opts_attr_ot_name,
+ &f_uac2_alt_opts_attr_fu_vol_name,
+ &f_uac2_alt_opts_attr_ssize,
+ &f_uac2_alt_opts_attr_chmask,
+ /* Playback does not have sync */
+ &f_uac2_alt_opts_attr_hs_bint,
+ &f_uac2_alt_opts_attr_terminal_type,
+
+ NULL,
+};
+
+
+static void f_uac2_alt_attr_release(struct config_item *item)
+{
+ struct f_uac2_alt_opts *alt_opts = to_f_uac2_alt_opts(item);
+
+ /* Opts 0 and 1 are fixed structures, 2+ are kzalloc'd */
+ if (alt_opts->c.alt_num > 1)
+ kfree(alt_opts);
+}
+
+static struct configfs_item_operations f_uac2_alt_item_ops = {
+ .release = f_uac2_alt_attr_release,
+};
+
+static const struct config_item_type alt_mode_c_type = {
+ .ct_item_ops = &f_uac2_alt_item_ops,
+ .ct_attrs = f_uac2_alt_attrs_c,
+ .ct_owner = THIS_MODULE,
+};
+
+static const struct config_item_type alt_mode_p_type = {
+ .ct_item_ops = &f_uac2_alt_item_ops,
+ .ct_attrs = f_uac2_alt_attrs_p,
+ .ct_owner = THIS_MODULE,
+};
+
+/*-------------------------------------------------------------------------*/
+
static void f_uac2_attr_release(struct config_item *item)
{
struct f_uac2_opts *opts = to_f_uac2_opts(item);
@@ -1889,8 +2045,111 @@ static struct configfs_attribute *f_uac2_attrs[] = {
NULL,
};
+static struct config_group *f_uac2_group_make(
+ struct config_group *group,
+ const char *name)
+{
+ struct f_uac2_opts *opts = to_f_uac2_opts(&group->cg_item);
+ struct f_uac2_alt_opts *alt_opts;
+ struct f_uac2_alt_opts *pos;
+ struct config_group *ret;
+ unsigned int alt_num;
+ int playback = 0;
+
+ mutex_lock(&opts->lock);
+ if (opts->refcnt) {
+ ret = ERR_PTR(-EBUSY);
+ goto end;
+ }
+
+ if (sscanf(name, "c_alt.%u", &alt_num) != 1) {
+ playback = 1;
+ if (sscanf(name, "p_alt.%u", &alt_num) != 1) {
+ ret = ERR_PTR(-EINVAL);
+ goto end;
+ }
+ }
+
+ if (alt_num > 255) {
+ ret = ERR_PTR(-EINVAL);
+ goto end;
+ }
+
+ /* Alt mode 0 has less properties */
+ if (alt_num == 0) {
+ struct f_uac2_alt_0_opts *alt_0_opts = (playback) ? &opts->p_alt_0_opts
+ : &opts->c_alt_0_opts;
+
+ init_alt_0_opts(alt_0_opts, opts, playback);
+ config_group_init_type_name(&alt_0_opts->c.group, name, &alt_mode_0_type);
+ ret = &alt_0_opts->c.group;
+ goto end;
+ }
+
+ if (alt_num == 1) {
+ /* Alt mode 1 always exists */
+ alt_opts = (playback) ? &opts->p_alt_1_opts : &opts->c_alt_1_opts;
+ } else {
+ /* Allocate a structure for alt mode 2+ */
+ alt_opts = kzalloc(sizeof(*alt_opts), GFP_KERNEL);
+ if (!alt_opts) {
+ ret = ERR_PTR(-ENOMEM);
+ goto end;
+ }
+ }
+
+ ret = &alt_opts->c.group;
+
+ config_group_init_type_name(&alt_opts->c.group, name, (playback) ? &alt_mode_p_type
+ : &alt_mode_c_type);
+
+ init_alt_opts(alt_opts, opts, alt_num, playback);
+
+ /* Alt mode 1 doesn't go in the list. It is handled separately to
+ * also handle the case where the alt.1 group is not created.
+ */
+ if (alt_num == 1)
+ goto end;
+
+ /* Insert the new alt mode in the list, sorted by alt_num */
+ list_for_each_entry(pos, (playback) ? &opts->p_alt_opts : &opts->c_alt_opts, list) {
+ if (alt_opts->c.alt_num < pos->c.alt_num) {
+ list_add_tail(&alt_opts->list, &pos->list);
+ mutex_unlock(&opts->lock);
+ goto end;
+ }
+ }
+
+ list_add_tail(&alt_opts->list, (playback) ? &opts->p_alt_opts : &opts->c_alt_opts);
+
+end:
+ mutex_unlock(&opts->lock);
+
+ return ret;
+}
+
+static void f_uac2_group_drop(struct config_group *group, struct config_item *item)
+{
+ struct f_uac2_alt_opts *alt_opts = to_f_uac2_alt_opts(item);
+
+ /* Alt modes 0 and 1 are preallocated and not included in the list */
+ if (alt_opts->c.alt_num > 1) {
+ mutex_lock(&alt_opts->c.opts->lock);
+ list_del(&alt_opts->list);
+ mutex_unlock(&alt_opts->c.opts->lock);
+ }
+
+ config_item_put(item);
+}
+
+static struct configfs_group_operations f_uac2_group_ops = {
+ .make_group = &f_uac2_group_make,
+ .drop_item = &f_uac2_group_drop,
+};
+
static const struct config_item_type f_uac2_func_type = {
.ct_item_ops = &f_uac2_item_ops,
+ .ct_group_ops = &f_uac2_group_ops,
.ct_attrs = f_uac2_attrs,
.ct_owner = THIS_MODULE,
};
@@ -1914,6 +2173,9 @@ static struct usb_function_instance *afunc_alloc_inst(void)
mutex_init(&opts->lock);
opts->func_inst.free_func_inst = afunc_free_inst;
+ INIT_LIST_HEAD(&opts->c_alt_opts);
+ INIT_LIST_HEAD(&opts->p_alt_opts);
+
config_group_init_type_name(&opts->func_inst.group, "",
&f_uac2_func_type);
@@ -1947,6 +2209,7 @@ static struct usb_function_instance *afunc_alloc_inst(void)
scnprintf(opts->clksrc_in_name, sizeof(opts->clksrc_in_name), "Input Clock");
scnprintf(opts->clksrc_out_name, sizeof(opts->clksrc_out_name), "Output Clock");
+ // Note: Strings are from the host perspective, opt prefixes are from the device perspective
scnprintf(opts->p_it_name, sizeof(opts->p_it_name), "USBD Out");
scnprintf(opts->p_it_ch_name, sizeof(opts->p_it_ch_name), "Capture Channels");
scnprintf(opts->p_ot_name, sizeof(opts->p_ot_name), "USBH In");
diff --git a/drivers/usb/gadget/function/u_uac2.h b/drivers/usb/gadget/function/u_uac2.h
index 0df808289ded..8c061e588324 100644
--- a/drivers/usb/gadget/function/u_uac2.h
+++ b/drivers/usb/gadget/function/u_uac2.h
@@ -40,8 +40,59 @@
#define UAC2_DEF_C_TERM_TYPE 0x201
/* UAC_INPUT_TERMINAL_MICROPHONE*/
+struct f_uac2_opts;
+
+struct f_uac2_alt_opts_common {
+ struct config_group group;
+ struct f_uac2_opts *opts;
+ u8 alt_num;
+};
+
+/* Alt mode 0 only has a name */
+struct f_uac2_alt_0_opts {
+ struct f_uac2_alt_opts_common c;
+
+ char name[USB_MAX_STRING_LEN];
+};
+
+/* Alt modes 1+ */
+struct f_uac2_alt_opts {
+ struct f_uac2_alt_opts_common c;
+
+ struct list_head list;
+
+ /* Strings */
+ char name[USB_MAX_STRING_LEN];
+ char it_name[USB_MAX_STRING_LEN];
+ char it_ch_name[USB_MAX_STRING_LEN];
+ char ot_name[USB_MAX_STRING_LEN];
+ char fu_vol_name[USB_MAX_STRING_LEN];
+
+ /* Audio options */
+ int chmask;
+ int ssize;
+ int sync;
+ u8 hs_bint;
+ s16 terminal_type;
+
+};
+
struct f_uac2_opts {
struct usb_function_instance func_inst;
+
+ /* Alt mode 0 options */
+ struct f_uac2_alt_0_opts c_alt_0_opts;
+ struct f_uac2_alt_0_opts p_alt_0_opts;
+
+ /* Alt mode 1 options */
+ struct f_uac2_alt_opts c_alt_1_opts;
+ struct f_uac2_alt_opts p_alt_1_opts;
+
+ /* Alt mode 2+ options */
+ struct list_head c_alt_opts;
+ struct list_head p_alt_opts;
+
+ /* Default options and Alt mode 1 if no c/p_alt.1 created */
int p_chmask;
int p_srates[UAC_MAX_RATES];
int p_ssize;
@@ -52,36 +103,36 @@ struct f_uac2_opts {
int c_sync;
u8 c_hs_bint;
- bool p_mute_present;
- bool p_volume_present;
+ bool p_mute_present;
+ bool p_volume_present;
s16 p_volume_min;
s16 p_volume_max;
s16 p_volume_res;
- bool c_mute_present;
- bool c_volume_present;
+ bool c_mute_present;
+ bool c_volume_present;
s16 c_volume_min;
s16 c_volume_max;
s16 c_volume_res;
int req_number;
int fb_max;
- bool bound;
-
- char function_name[USB_MAX_STRING_LEN];
- char if_ctrl_name[USB_MAX_STRING_LEN];
- char clksrc_in_name[USB_MAX_STRING_LEN];
- char clksrc_out_name[USB_MAX_STRING_LEN];
-
- char p_it_name[USB_MAX_STRING_LEN];
- char p_it_ch_name[USB_MAX_STRING_LEN];
- char p_ot_name[USB_MAX_STRING_LEN];
- char p_fu_vol_name[USB_MAX_STRING_LEN];
-
- char c_it_name[USB_MAX_STRING_LEN];
- char c_it_ch_name[USB_MAX_STRING_LEN];
- char c_ot_name[USB_MAX_STRING_LEN];
- char c_fu_vol_name[USB_MAX_STRING_LEN];
+ bool bound;
+
+ char function_name[USB_MAX_STRING_LEN];
+ char if_ctrl_name[USB_MAX_STRING_LEN];
+ char clksrc_in_name[USB_MAX_STRING_LEN];
+ char clksrc_out_name[USB_MAX_STRING_LEN];
+
+ char p_it_name[USB_MAX_STRING_LEN];
+ char p_it_ch_name[USB_MAX_STRING_LEN];
+ char p_ot_name[USB_MAX_STRING_LEN];
+ char p_fu_vol_name[USB_MAX_STRING_LEN];
+
+ char c_it_name[USB_MAX_STRING_LEN];
+ char c_it_ch_name[USB_MAX_STRING_LEN];
+ char c_ot_name[USB_MAX_STRING_LEN];
+ char c_fu_vol_name[USB_MAX_STRING_LEN];
s16 p_terminal_type;
s16 c_terminal_type;
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH RFC 09/14] usb: gadget: f_uac1: Make string table dynamic with strings from all alt modes
2024-09-28 15:08 [PATCH RFC 00/14] usb: gadget: f_uac: Add support for alt mode settings crwulff
` (7 preceding siblings ...)
2024-09-28 15:08 ` [PATCH RFC 08/14] usb: gadget: f_uac2: " crwulff
@ 2024-09-28 15:09 ` crwulff
2024-09-28 15:09 ` [PATCH RFC 10/14] usb: gadget: f_uac2: " crwulff
` (4 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: crwulff @ 2024-09-28 15:09 UTC (permalink / raw)
To: linux-usb
Cc: Pavel Hofman, Greg Kroah-Hartman, James Gruber, Jeff Johnson,
John Keeping, Jonathan Corbet, Lee Jones, Perr Zhang, linux-doc,
linux-kernel, Chris Wulff
From: Chris Wulff <crwulff@gmail.com>
The number of strings is now dependent on how many alt modes exist and
what their strings are set to. This allows the strings to be dynamically
consolidated where alt modes use the same strings, or separate when
different strings are configured in some alt modes.
Signed-off-by: Chris Wulff <crwulff@gmail.com>
---
drivers/usb/gadget/function/f_uac1.c | 168 ++++++++++++++++++---------
1 file changed, 110 insertions(+), 58 deletions(-)
diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c
index e7bfc32387bf..3484aa237354 100644
--- a/drivers/usb/gadget/function/f_uac1.c
+++ b/drivers/usb/gadget/function/f_uac1.c
@@ -498,35 +498,6 @@ static struct usb_descriptor_header *f_audio_ss_desc[] = {
NULL,
};
-enum {
- STR_AC_IF,
- STR_USB_OUT_IT,
- STR_USB_OUT_IT_CH_NAMES,
- STR_IO_OUT_OT,
- STR_IO_IN_IT,
- STR_IO_IN_IT_CH_NAMES,
- STR_USB_IN_OT,
- STR_FU_IN,
- STR_FU_OUT,
- STR_AS_OUT_IF_ALT0,
- STR_AS_OUT_IF_ALT1,
- STR_AS_IN_IF_ALT0,
- STR_AS_IN_IF_ALT1,
- NUM_STR_DESCRIPTORS,
-};
-
-static struct usb_string strings_uac1[NUM_STR_DESCRIPTORS + 1] = {};
-
-static struct usb_gadget_strings str_uac1 = {
- .language = 0x0409, /* en-us */
- .strings = strings_uac1,
-};
-
-static struct usb_gadget_strings *uac1_strings[] = {
- &str_uac1,
- NULL,
-};
-
/*
* This function is an ALSA sound card following USB Audio Class Spec 1.0.
*/
@@ -1163,6 +1134,93 @@ f_audio_suspend(struct usb_function *f)
/*-------------------------------------------------------------------------*/
+/*
+ * String handling
+ */
+
+#define MAX_STRINGS 256
+
+static int add_string(struct usb_string *strings, const char *s)
+{
+ int i;
+
+ if (!s || s[0] == '\0')
+ return 0;
+
+ for (i = 0; i < MAX_STRINGS; i++) {
+ if (!strings[i].s) {
+ strings[i].s = s;
+ return 0; /* IDs aren't allocated yet */
+ }
+
+ if (!strcmp(s, strings[i].s))
+ return strings[i].id;
+ }
+
+ return -1;
+}
+
+static void add_alt_strings(struct usb_string *strings, struct f_uac1_alt_opts *alt_opts, bool fu)
+{
+ add_string(strings, alt_opts->name);
+ add_string(strings, alt_opts->it_name);
+ add_string(strings, alt_opts->it_ch_name);
+ add_string(strings, alt_opts->ot_name);
+ if (fu)
+ add_string(strings, alt_opts->fu_vol_name);
+}
+
+static struct usb_string *attach_strings(struct usb_composite_dev *cdev,
+ struct f_uac1_opts *audio_opts)
+{
+ struct usb_string *strings = kzalloc(sizeof(struct usb_string) * MAX_STRINGS,
+ GFP_KERNEL);
+ struct f_uac1_alt_opts *alt_opts;
+ struct usb_string *us;
+ int strings_uac1_length;
+
+ struct usb_gadget_strings str_uac1 = {
+ .language = 0x0409, /* en-us */
+ .strings = strings
+ };
+
+ struct usb_gadget_strings *uac1_strings[] = {
+ &str_uac1,
+ NULL,
+ };
+
+ if (!strings)
+ return ERR_PTR(-ENOMEM);
+
+ /* Add all the strings from all the alt mode options */
+ add_string(strings, audio_opts->function_name);
+ add_string(strings, audio_opts->c_alt_0_opts.name);
+ add_string(strings, audio_opts->p_alt_0_opts.name);
+ add_alt_strings(strings, &audio_opts->c_alt_1_opts, FUOUT_EN(audio_opts));
+ add_alt_strings(strings, &audio_opts->p_alt_1_opts, FUIN_EN(audio_opts));
+ list_for_each_entry(alt_opts, &audio_opts->c_alt_opts, list) {
+ add_alt_strings(strings, alt_opts, FUOUT_EN(audio_opts));
+ }
+ list_for_each_entry(alt_opts, &audio_opts->p_alt_opts, list) {
+ add_alt_strings(strings, alt_opts, FUIN_EN(audio_opts));
+ }
+
+ for (strings_uac1_length = 0; strings[strings_uac1_length].s; strings_uac1_length++)
+ ;
+
+ /* Attach strings to the composite device and get string IDs assigned */
+ us = usb_gstrings_attach(cdev, uac1_strings, strings_uac1_length);
+
+ /* Strings are now copied to the composite device and we use the
+ * copy in "us" going forward, that has all the string IDs.
+ */
+ kfree(strings);
+
+ return us;
+}
+
+/*-------------------------------------------------------------------------*/
+
static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac1_alt_opts *alt_opts,
struct usb_endpoint_descriptor *ep_desc,
enum usb_device_speed speed, bool is_playback)
@@ -1526,23 +1584,11 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
if (status)
return status;
- strings_uac1[STR_AC_IF].s = audio_opts->function_name;
-
- strings_uac1[STR_USB_OUT_IT].s = audio_opts->c_it_name;
- strings_uac1[STR_USB_OUT_IT_CH_NAMES].s = audio_opts->c_it_ch_name;
- strings_uac1[STR_IO_OUT_OT].s = audio_opts->c_ot_name;
- strings_uac1[STR_FU_OUT].s = audio_opts->c_fu_vol_name;
- strings_uac1[STR_AS_OUT_IF_ALT0].s = "Playback Inactive";
- strings_uac1[STR_AS_OUT_IF_ALT1].s = "Playback Active";
-
- strings_uac1[STR_IO_IN_IT].s = audio_opts->p_it_name;
- strings_uac1[STR_IO_IN_IT_CH_NAMES].s = audio_opts->p_it_ch_name;
- strings_uac1[STR_USB_IN_OT].s = audio_opts->p_ot_name;
- strings_uac1[STR_FU_IN].s = audio_opts->p_fu_vol_name;
- strings_uac1[STR_AS_IN_IF_ALT0].s = "Capture Inactive";
- strings_uac1[STR_AS_IN_IF_ALT1].s = "Capture Active";
+ /* Past this point, all settings that apply to an alt mode should
+ * be used from their alt mode opts.
+ */
- us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1));
+ us = attach_strings(cdev, audio_opts);
if (IS_ERR(us))
return PTR_ERR(us);
@@ -1565,31 +1611,37 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
}
}
- ac_interface_desc.iInterface = us[STR_AC_IF].id;
- usb_out_it_desc.iTerminal = us[STR_USB_OUT_IT].id;
- usb_out_it_desc.iChannelNames = us[STR_USB_OUT_IT_CH_NAMES].id;
- io_out_ot_desc.iTerminal = us[STR_IO_OUT_OT].id;
- as_out_interface_alt_0_desc.iInterface = us[STR_AS_OUT_IF_ALT0].id;
- as_out_interface_alt_1_desc.iInterface = us[STR_AS_OUT_IF_ALT1].id;
- io_in_it_desc.iTerminal = us[STR_IO_IN_IT].id;
- io_in_it_desc.iChannelNames = us[STR_IO_IN_IT_CH_NAMES].id;
- usb_in_ot_desc.iTerminal = us[STR_USB_IN_OT].id;
- as_in_interface_alt_0_desc.iInterface = us[STR_AS_IN_IF_ALT0].id;
- as_in_interface_alt_1_desc.iInterface = us[STR_AS_IN_IF_ALT1].id;
+ ac_interface_desc.iInterface = add_string(us, audio_opts->function_name);
+ usb_out_it_desc.iTerminal = add_string(us, audio_opts->c_alt_1_opts.it_name);
+ usb_out_it_desc.iChannelNames = add_string(us, audio_opts->c_alt_1_opts.it_ch_name);
+ io_out_ot_desc.iTerminal = add_string(us, audio_opts->c_alt_1_opts.ot_name);
+ as_out_interface_alt_0_desc.iInterface = add_string(us, audio_opts->c_alt_0_opts.name);
+ as_out_interface_alt_1_desc.iInterface = add_string(us, audio_opts->c_alt_1_opts.name);
+ io_in_it_desc.iTerminal = add_string(us, audio_opts->p_alt_1_opts.it_name);
+ io_in_it_desc.iChannelNames = add_string(us, audio_opts->p_alt_1_opts.it_ch_name);
+ usb_in_ot_desc.iTerminal = add_string(us, audio_opts->p_alt_1_opts.ot_name);
+ as_in_interface_alt_0_desc.iInterface = add_string(us, audio_opts->p_alt_0_opts.name);
+ as_in_interface_alt_1_desc.iInterface = add_string(us, audio_opts->p_alt_1_opts.name);
if (FUOUT_EN(audio_opts)) {
u8 *i_feature;
i_feature = (u8 *)out_feature_unit_desc +
out_feature_unit_desc->bLength - 1;
- *i_feature = us[STR_FU_OUT].id;
+ *i_feature = add_string(us, audio_opts->c_alt_1_opts.fu_vol_name);
}
if (FUIN_EN(audio_opts)) {
u8 *i_feature;
i_feature = (u8 *)in_feature_unit_desc +
in_feature_unit_desc->bLength - 1;
- *i_feature = us[STR_FU_IN].id;
+ *i_feature = add_string(us, audio_opts->p_alt_1_opts.fu_vol_name);
+ }
+
+ us = attach_strings(cdev, audio_opts);
+ if (IS_ERR(us)) {
+ status = PTR_ERR(us);
+ goto fail;
}
/* Set channel numbers */
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH RFC 10/14] usb: gadget: f_uac2: Make string table dynamic with strings from all alt modes
2024-09-28 15:08 [PATCH RFC 00/14] usb: gadget: f_uac: Add support for alt mode settings crwulff
` (8 preceding siblings ...)
2024-09-28 15:09 ` [PATCH RFC 09/14] usb: gadget: f_uac1: Make string table dynamic with strings from all alt modes crwulff
@ 2024-09-28 15:09 ` crwulff
2024-09-28 15:09 ` [PATCH RFC 11/14] usb: gadget: f_uac1: Generate dynamic descriptors based on alt opts crwulff
` (3 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: crwulff @ 2024-09-28 15:09 UTC (permalink / raw)
To: linux-usb
Cc: Pavel Hofman, Greg Kroah-Hartman, James Gruber, Jeff Johnson,
John Keeping, Jonathan Corbet, Lee Jones, Perr Zhang, linux-doc,
linux-kernel, Chris Wulff
From: Chris Wulff <crwulff@gmail.com>
The number of strings is now dependent on how many alt modes exist and
what their strings are set to. This allows the strings to be dynamically
consolidated where alt modes use the same strings, or separate when
different strings are configured in some alt modes.
Signed-off-by: Chris Wulff <crwulff@gmail.com>
---
drivers/usb/gadget/function/f_uac2.c | 181 +++++++++++++++++----------
1 file changed, 113 insertions(+), 68 deletions(-)
diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index e9f951215c26..54702888855d 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -93,38 +93,6 @@ static int afunc_notify(struct g_audio *agdev, int unit_id, int cs);
/* --------- USB Function Interface ------------- */
-enum {
- STR_ASSOC,
- STR_IF_CTRL,
- STR_CLKSRC_IN,
- STR_CLKSRC_OUT,
- STR_USB_IT,
- STR_USB_IT_CH,
- STR_IO_IT,
- STR_IO_IT_CH,
- STR_USB_OT,
- STR_IO_OT,
- STR_FU_IN,
- STR_FU_OUT,
- STR_AS_OUT_ALT0,
- STR_AS_OUT_ALT1,
- STR_AS_IN_ALT0,
- STR_AS_IN_ALT1,
- NUM_STR_DESCRIPTORS,
-};
-
-static struct usb_string strings_fn[NUM_STR_DESCRIPTORS + 1] = {};
-
-static struct usb_gadget_strings str_fn = {
- .language = 0x0409, /* en-us */
- .strings = strings_fn,
-};
-
-static struct usb_gadget_strings *fn_strings[] = {
- &str_fn,
- NULL,
-};
-
static struct usb_interface_assoc_descriptor iad_desc = {
.bLength = sizeof iad_desc,
.bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
@@ -649,6 +617,98 @@ struct cntrl_subrange_lay3 {
DECLARE_UAC2_CNTRL_RANGES_LAY3(srates, UAC_MAX_RATES);
+/*-------------------------------------------------------------------------*/
+
+/*
+ * String handling
+ */
+
+#define MAX_STRINGS 256
+
+static int add_string(struct usb_string *strings, const char *s)
+{
+ int i;
+
+ if (!s || s[0] == '\0')
+ return 0;
+
+ for (i = 0; i < MAX_STRINGS; i++) {
+ if (!strings[i].s) {
+ strings[i].s = s;
+ return 0; /* IDs aren't allocated yet */
+ }
+
+ if (!strcmp(s, strings[i].s))
+ return strings[i].id;
+ }
+
+ return -1;
+}
+
+static void add_alt_strings(struct usb_string *strings, struct f_uac2_alt_opts *alt_opts, bool fu)
+{
+ add_string(strings, alt_opts->name);
+ add_string(strings, alt_opts->it_name);
+ add_string(strings, alt_opts->it_ch_name);
+ add_string(strings, alt_opts->ot_name);
+ if (fu)
+ add_string(strings, alt_opts->fu_vol_name);
+}
+
+static struct usb_string *attach_strings(struct usb_composite_dev *cdev,
+ struct f_uac2_opts *audio_opts)
+{
+ struct usb_string *strings = kzalloc(sizeof(struct usb_string) * MAX_STRINGS,
+ GFP_KERNEL);
+ struct f_uac2_alt_opts *alt_opts;
+ struct usb_string *us;
+ int strings_fn_length;
+
+ struct usb_gadget_strings strings_fn = {
+ .language = 0x0409, /* en-us */
+ .strings = strings
+ };
+
+ struct usb_gadget_strings *fn_strings[] = {
+ &strings_fn,
+ NULL,
+ };
+
+ if (!strings)
+ return ERR_PTR(-ENOMEM);
+
+ /* Add all the strings from all the alt mode options */
+ add_string(strings, audio_opts->function_name);
+ add_string(strings, audio_opts->if_ctrl_name);
+ add_string(strings, audio_opts->clksrc_in_name);
+ add_string(strings, audio_opts->clksrc_out_name);
+ add_string(strings, audio_opts->c_alt_0_opts.name);
+ add_string(strings, audio_opts->p_alt_0_opts.name);
+ add_alt_strings(strings, &audio_opts->c_alt_1_opts, FUOUT_EN(audio_opts));
+ add_alt_strings(strings, &audio_opts->p_alt_1_opts, FUIN_EN(audio_opts));
+ list_for_each_entry(alt_opts, &audio_opts->c_alt_opts, list) {
+ add_alt_strings(strings, alt_opts, FUOUT_EN(audio_opts));
+ }
+ list_for_each_entry(alt_opts, &audio_opts->p_alt_opts, list) {
+ add_alt_strings(strings, alt_opts, FUIN_EN(audio_opts));
+ }
+
+ for (strings_fn_length = 0; strings[strings_fn_length].s; strings_fn_length++)
+ ;
+
+ /* Attach strings to the composite device and get string IDs assigned */
+ us = usb_gstrings_attach(cdev, fn_strings, strings_fn_length);
+
+ /* Strings are now copied to the composite device and we use the
+ * copy in "us" going forward, that has all the string IDs.
+ */
+ kfree(strings);
+
+ return us;
+}
+
+/*-------------------------------------------------------------------------*/
+
static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac2_alt_opts *alt_opts,
struct usb_endpoint_descriptor *ep_desc,
enum usb_device_speed speed, bool is_playback)
@@ -994,26 +1054,11 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
if (ret)
return ret;
- strings_fn[STR_ASSOC].s = uac2_opts->function_name;
- strings_fn[STR_IF_CTRL].s = uac2_opts->if_ctrl_name;
- strings_fn[STR_CLKSRC_IN].s = uac2_opts->clksrc_in_name;
- strings_fn[STR_CLKSRC_OUT].s = uac2_opts->clksrc_out_name;
-
- strings_fn[STR_USB_IT].s = uac2_opts->c_it_name;
- strings_fn[STR_USB_IT_CH].s = uac2_opts->c_it_ch_name;
- strings_fn[STR_IO_OT].s = uac2_opts->c_ot_name;
- strings_fn[STR_FU_OUT].s = uac2_opts->c_fu_vol_name;
- strings_fn[STR_AS_OUT_ALT0].s = "Playback Inactive";
- strings_fn[STR_AS_OUT_ALT1].s = "Playback Active";
-
- strings_fn[STR_IO_IT].s = uac2_opts->p_it_name;
- strings_fn[STR_IO_IT_CH].s = uac2_opts->p_it_ch_name;
- strings_fn[STR_USB_OT].s = uac2_opts->p_ot_name;
- strings_fn[STR_FU_IN].s = uac2_opts->p_fu_vol_name;
- strings_fn[STR_AS_IN_ALT0].s = "Capture Inactive";
- strings_fn[STR_AS_IN_ALT1].s = "Capture Active";
-
- us = usb_gstrings_attach(cdev, fn_strings, ARRAY_SIZE(strings_fn));
+ /* Past this point, all settings that apply to an alt mode should
+ * be used from their alt mode opts.
+ */
+
+ us = attach_strings(cdev, uac2_opts);
if (IS_ERR(us))
return PTR_ERR(us);
@@ -1030,30 +1075,30 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
}
}
- iad_desc.iFunction = us[STR_ASSOC].id;
- std_ac_if_desc.iInterface = us[STR_IF_CTRL].id;
- in_clk_src_desc.iClockSource = us[STR_CLKSRC_IN].id;
- out_clk_src_desc.iClockSource = us[STR_CLKSRC_OUT].id;
- usb_out_it_desc.iTerminal = us[STR_USB_IT].id;
- usb_out_it_desc.iChannelNames = us[STR_USB_IT_CH].id;
- io_in_it_desc.iTerminal = us[STR_IO_IT].id;
- io_in_it_desc.iChannelNames = us[STR_IO_IT_CH].id;
- usb_in_ot_desc.iTerminal = us[STR_USB_OT].id;
- io_out_ot_desc.iTerminal = us[STR_IO_OT].id;
- std_as_out_if0_desc.iInterface = us[STR_AS_OUT_ALT0].id;
- std_as_out_if1_desc.iInterface = us[STR_AS_OUT_ALT1].id;
- std_as_in_if0_desc.iInterface = us[STR_AS_IN_ALT0].id;
- std_as_in_if1_desc.iInterface = us[STR_AS_IN_ALT1].id;
+ iad_desc.iFunction = add_string(us, uac2_opts->function_name);
+ std_ac_if_desc.iInterface = add_string(us, uac2_opts->if_ctrl_name);
+ in_clk_src_desc.iClockSource = add_string(us, uac2_opts->clksrc_in_name);
+ out_clk_src_desc.iClockSource = add_string(us, uac2_opts->clksrc_out_name);
+ usb_out_it_desc.iTerminal = add_string(us, uac2_opts->c_alt_1_opts.it_name);
+ usb_out_it_desc.iChannelNames = add_string(us, uac2_opts->c_alt_1_opts.it_ch_name);
+ io_in_it_desc.iTerminal = add_string(us, uac2_opts->p_alt_1_opts.it_name);
+ io_in_it_desc.iChannelNames = add_string(us, uac2_opts->p_alt_1_opts.it_ch_name);
+ usb_in_ot_desc.iTerminal = add_string(us, uac2_opts->p_alt_1_opts.ot_name);
+ io_out_ot_desc.iTerminal = add_string(us, uac2_opts->c_alt_1_opts.ot_name);
+ std_as_out_if0_desc.iInterface = add_string(us, uac2_opts->c_alt_0_opts.name);
+ std_as_out_if1_desc.iInterface = add_string(us, uac2_opts->c_alt_1_opts.name);
+ std_as_in_if0_desc.iInterface = add_string(us, uac2_opts->p_alt_0_opts.name);
+ std_as_in_if1_desc.iInterface = add_string(us, uac2_opts->p_alt_0_opts.name);
if (FUOUT_EN(uac2_opts)) {
u8 *i_feature = (u8 *)out_feature_unit_desc +
out_feature_unit_desc->bLength - 1;
- *i_feature = us[STR_FU_OUT].id;
+ *i_feature = add_string(us, uac2_opts->c_alt_1_opts.fu_vol_name);
}
if (FUIN_EN(uac2_opts)) {
u8 *i_feature = (u8 *)in_feature_unit_desc +
in_feature_unit_desc->bLength - 1;
- *i_feature = us[STR_FU_IN].id;
+ *i_feature = add_string(us, uac2_opts->p_alt_1_opts.fu_vol_name);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH RFC 11/14] usb: gadget: f_uac1: Generate dynamic descriptors based on alt opts
2024-09-28 15:08 [PATCH RFC 00/14] usb: gadget: f_uac: Add support for alt mode settings crwulff
` (9 preceding siblings ...)
2024-09-28 15:09 ` [PATCH RFC 10/14] usb: gadget: f_uac2: " crwulff
@ 2024-09-28 15:09 ` crwulff
2024-09-28 15:09 ` [PATCH RFC 12/14] usb: gadget: f_uac2: " crwulff
` (2 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: crwulff @ 2024-09-28 15:09 UTC (permalink / raw)
To: linux-usb
Cc: Pavel Hofman, Greg Kroah-Hartman, James Gruber, Jeff Johnson,
John Keeping, Jonathan Corbet, Lee Jones, Perr Zhang, linux-doc,
linux-kernel, Chris Wulff
From: Chris Wulff <crwulff@gmail.com>
Descriptors are now generated based on what alt modes are configured.
The lifetime of allocations has changed a bit with this such that we
deallocate our copy of the descriptors as soon as they've been registered.
Many of the descriptors that were static are now attached to their alt mode
opts and initialized with a function.
Signed-off-by: Chris Wulff <crwulff@gmail.com>
---
drivers/usb/gadget/function/f_uac1.c | 1390 +++++++++++++-------------
drivers/usb/gadget/function/u_uac1.h | 38 +
2 files changed, 709 insertions(+), 719 deletions(-)
diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c
index 3484aa237354..7803957e4f82 100644
--- a/drivers/usb/gadget/function/f_uac1.c
+++ b/drivers/usb/gadget/function/f_uac1.c
@@ -27,16 +27,36 @@
/* UAC1 spec: 3.7.2.3 Audio Channel Cluster Format */
#define UAC1_CHANNEL_MASK 0x0FFF
-#define USB_OUT_FU_ID (out_feature_unit_desc->bUnitID)
-#define USB_IN_FU_ID (in_feature_unit_desc->bUnitID)
+#define USB_OUT_FU_ID(_opts) (_opts->c_alt_1_opts.fu_id)
+#define USB_IN_FU_ID(_opts) (_opts->p_alt_1_opts.fu_id)
-#define EPIN_EN(_opts) ((_opts)->p_chmask != 0)
-#define EPOUT_EN(_opts) ((_opts)->c_chmask != 0)
+#define EP_EN(_alt_opts) ((_alt_opts) && ((_alt_opts)->chmask != 0))
#define FUIN_EN(_opts) ((_opts)->p_mute_present \
|| (_opts)->p_volume_present)
#define FUOUT_EN(_opts) ((_opts)->c_mute_present \
|| (_opts)->c_volume_present)
-#define EPOUT_FBACK_IN_EN(_opts) ((_opts)->c_sync == USB_ENDPOINT_SYNC_ASYNC)
+#define EPOUT_FBACK_IN_EN(_alt_opts) ((_alt_opts)->sync == USB_ENDPOINT_SYNC_ASYNC)
+
+/* Check if any alt mode has option enabled */
+#define EN_ANY(single, fn, cp) \
+static int fn(struct f_uac1_opts *opts) \
+{ \
+ struct f_uac1_alt_opts *alt_opts; \
+ \
+ if (single(&opts->cp##_alt_1_opts)) \
+ return 1; \
+ \
+ list_for_each_entry(alt_opts, &opts->cp##_alt_opts, list) { \
+ if (single(alt_opts)) \
+ return 1; \
+ } \
+ \
+ return 0; \
+}
+
+EN_ANY(EP_EN, epout_en_any, c)
+EN_ANY(EP_EN, epin_en_any, p)
+EN_ANY(EPOUT_FBACK_IN_EN, epout_fback_in_en_any, p)
struct f_uac1 {
struct g_audio g_audio;
@@ -88,49 +108,6 @@ static struct usb_interface_descriptor ac_interface_desc = {
/* B.3.2 Class-Specific AC Interface Descriptor */
static struct uac1_ac_header_descriptor *ac_header_desc;
-static struct uac_input_terminal_descriptor usb_out_it_desc = {
- .bLength = UAC_DT_INPUT_TERMINAL_SIZE,
- .bDescriptorType = USB_DT_CS_INTERFACE,
- .bDescriptorSubtype = UAC_INPUT_TERMINAL,
- /* .bTerminalID = DYNAMIC */
- .wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
- .bAssocTerminal = 0,
- .wChannelConfig = cpu_to_le16(0x3),
-};
-
-static struct uac1_output_terminal_descriptor io_out_ot_desc = {
- .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE,
- .bDescriptorType = USB_DT_CS_INTERFACE,
- .bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
- /* .bTerminalID = DYNAMIC */
- .wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_SPEAKER),
- .bAssocTerminal = 0,
- /* .bSourceID = DYNAMIC */
-};
-
-static struct uac_input_terminal_descriptor io_in_it_desc = {
- .bLength = UAC_DT_INPUT_TERMINAL_SIZE,
- .bDescriptorType = USB_DT_CS_INTERFACE,
- .bDescriptorSubtype = UAC_INPUT_TERMINAL,
- /* .bTerminalID = DYNAMIC */
- .wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_MICROPHONE),
- .bAssocTerminal = 0,
- .wChannelConfig = cpu_to_le16(0x3),
-};
-
-static struct uac1_output_terminal_descriptor usb_in_ot_desc = {
- .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE,
- .bDescriptorType = USB_DT_CS_INTERFACE,
- .bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
- /* .bTerminalID = DYNAMIC */
- .wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
- .bAssocTerminal = 0,
- /* .bSourceID = DYNAMIC */
-};
-
-static struct uac_feature_unit_descriptor *in_feature_unit_desc;
-static struct uac_feature_unit_descriptor *out_feature_unit_desc;
-
/* AC IN Interrupt Endpoint */
static struct usb_endpoint_descriptor fs_ac_int_ep_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
@@ -167,97 +144,6 @@ static struct usb_ss_ep_comp_descriptor ss_ac_int_ep_desc_comp = {
.wBytesPerInterval = cpu_to_le16(2),
};
-/* B.4.1 Standard AS Interface Descriptor */
-static struct usb_interface_descriptor as_out_interface_alt_0_desc = {
- .bLength = USB_DT_INTERFACE_SIZE,
- .bDescriptorType = USB_DT_INTERFACE,
- .bAlternateSetting = 0,
- .bNumEndpoints = 0,
- .bInterfaceClass = USB_CLASS_AUDIO,
- .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
-};
-
-static struct usb_interface_descriptor as_out_interface_alt_1_desc = {
- .bLength = USB_DT_INTERFACE_SIZE,
- .bDescriptorType = USB_DT_INTERFACE,
- .bAlternateSetting = 1,
- .bNumEndpoints = 1,
- .bInterfaceClass = USB_CLASS_AUDIO,
- .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
-};
-
-static struct usb_interface_descriptor as_in_interface_alt_0_desc = {
- .bLength = USB_DT_INTERFACE_SIZE,
- .bDescriptorType = USB_DT_INTERFACE,
- .bAlternateSetting = 0,
- .bNumEndpoints = 0,
- .bInterfaceClass = USB_CLASS_AUDIO,
- .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
-};
-
-static struct usb_interface_descriptor as_in_interface_alt_1_desc = {
- .bLength = USB_DT_INTERFACE_SIZE,
- .bDescriptorType = USB_DT_INTERFACE,
- .bAlternateSetting = 1,
- .bNumEndpoints = 1,
- .bInterfaceClass = USB_CLASS_AUDIO,
- .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
-};
-
-/* B.4.2 Class-Specific AS Interface Descriptor */
-static struct uac1_as_header_descriptor as_out_header_desc = {
- .bLength = UAC_DT_AS_HEADER_SIZE,
- .bDescriptorType = USB_DT_CS_INTERFACE,
- .bDescriptorSubtype = UAC_AS_GENERAL,
- /* .bTerminalLink = DYNAMIC */
- .bDelay = 1,
- .wFormatTag = cpu_to_le16(UAC_FORMAT_TYPE_I_PCM),
-};
-
-static struct uac1_as_header_descriptor as_in_header_desc = {
- .bLength = UAC_DT_AS_HEADER_SIZE,
- .bDescriptorType = USB_DT_CS_INTERFACE,
- .bDescriptorSubtype = UAC_AS_GENERAL,
- /* .bTerminalLink = DYNAMIC */
- .bDelay = 1,
- .wFormatTag = cpu_to_le16(UAC_FORMAT_TYPE_I_PCM),
-};
-
-DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(UAC_MAX_RATES);
-#define uac_format_type_i_discrete_descriptor \
- uac_format_type_i_discrete_descriptor_##UAC_MAX_RATES
-
-static struct uac_format_type_i_discrete_descriptor as_out_type_i_desc = {
- .bLength = 0, /* filled on rate setup */
- .bDescriptorType = USB_DT_CS_INTERFACE,
- .bDescriptorSubtype = UAC_FORMAT_TYPE,
- .bFormatType = UAC_FORMAT_TYPE_I,
- .bSubframeSize = 2,
- .bBitResolution = 16,
- .bSamFreqType = 0, /* filled on rate setup */
-};
-
-/* Standard ISO OUT Endpoint Descriptor */
-static struct usb_endpoint_descriptor fs_as_out_ep_desc = {
- .bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
- .bEndpointAddress = USB_DIR_OUT,
- .bmAttributes = USB_ENDPOINT_SYNC_ADAPTIVE
- | USB_ENDPOINT_XFER_ISOC,
- .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
- .bInterval = 1,
-};
-
-static struct usb_endpoint_descriptor hs_as_out_ep_desc = {
- .bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
- .bEndpointAddress = USB_DIR_OUT,
- .bmAttributes = USB_ENDPOINT_SYNC_ADAPTIVE
- | USB_ENDPOINT_XFER_ISOC,
- .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
- .bInterval = 4,
-};
-
/* Class-specific AS ISO OUT Endpoint Descriptor */
static struct uac_iso_endpoint_descriptor as_iso_out_desc = {
.bLength = UAC_ISO_ENDPOINT_DESC_SIZE,
@@ -268,37 +154,6 @@ static struct uac_iso_endpoint_descriptor as_iso_out_desc = {
.wLockDelay = cpu_to_le16(1),
};
-static struct uac_format_type_i_discrete_descriptor as_in_type_i_desc = {
- .bLength = 0, /* filled on rate setup */
- .bDescriptorType = USB_DT_CS_INTERFACE,
- .bDescriptorSubtype = UAC_FORMAT_TYPE,
- .bFormatType = UAC_FORMAT_TYPE_I,
- .bSubframeSize = 2,
- .bBitResolution = 16,
- .bSamFreqType = 0, /* filled on rate setup */
-};
-
-/* Standard ISO IN Endpoint Descriptor */
-static struct usb_endpoint_descriptor fs_as_in_ep_desc = {
- .bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
- .bEndpointAddress = USB_DIR_IN,
- .bmAttributes = USB_ENDPOINT_SYNC_ASYNC
- | USB_ENDPOINT_XFER_ISOC,
- .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
- .bInterval = 1,
-};
-
-static struct usb_endpoint_descriptor hs_as_in_ep_desc = {
- .bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
- .bEndpointAddress = USB_DIR_IN,
- .bmAttributes = USB_ENDPOINT_SYNC_ASYNC
- | USB_ENDPOINT_XFER_ISOC,
- .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
- .bInterval = 4,
-};
-
/* Class-specific AS ISO IN Endpoint Descriptor */
static struct uac_iso_endpoint_descriptor as_iso_in_desc = {
.bLength = UAC_ISO_ENDPOINT_DESC_SIZE,
@@ -351,153 +206,6 @@ static struct usb_ss_ep_comp_descriptor ss_as_in_fback_desc_comp = {
.wBytesPerInterval = cpu_to_le16(4),
};
-static struct usb_descriptor_header *f_audio_fs_desc[] = {
- (struct usb_descriptor_header *)&ac_interface_desc,
- (struct usb_descriptor_header *)&ac_header_desc,
-
- (struct usb_descriptor_header *)&usb_out_it_desc,
- (struct usb_descriptor_header *)&io_out_ot_desc,
- (struct usb_descriptor_header *)&out_feature_unit_desc,
-
- (struct usb_descriptor_header *)&io_in_it_desc,
- (struct usb_descriptor_header *)&usb_in_ot_desc,
- (struct usb_descriptor_header *)&in_feature_unit_desc,
-
- (struct usb_descriptor_header *)&fs_ac_int_ep_desc,
-
- (struct usb_descriptor_header *)&as_out_interface_alt_0_desc,
- (struct usb_descriptor_header *)&as_out_interface_alt_1_desc,
- (struct usb_descriptor_header *)&as_out_header_desc,
-
- (struct usb_descriptor_header *)&as_out_type_i_desc,
-
- (struct usb_descriptor_header *)&fs_as_out_ep_desc,
- (struct usb_descriptor_header *)&as_iso_out_desc,
- (struct usb_descriptor_header *)&fs_as_in_fback_desc,
-
- (struct usb_descriptor_header *)&as_in_interface_alt_0_desc,
- (struct usb_descriptor_header *)&as_in_interface_alt_1_desc,
- (struct usb_descriptor_header *)&as_in_header_desc,
-
- (struct usb_descriptor_header *)&as_in_type_i_desc,
-
- (struct usb_descriptor_header *)&fs_as_in_ep_desc,
- (struct usb_descriptor_header *)&as_iso_in_desc,
- NULL,
-};
-
-static struct usb_descriptor_header *f_audio_hs_desc[] = {
- (struct usb_descriptor_header *)&ac_interface_desc,
- (struct usb_descriptor_header *)&ac_header_desc,
-
- (struct usb_descriptor_header *)&usb_out_it_desc,
- (struct usb_descriptor_header *)&io_out_ot_desc,
- (struct usb_descriptor_header *)&out_feature_unit_desc,
-
- (struct usb_descriptor_header *)&io_in_it_desc,
- (struct usb_descriptor_header *)&usb_in_ot_desc,
- (struct usb_descriptor_header *)&in_feature_unit_desc,
-
- (struct usb_descriptor_header *)&hs_ac_int_ep_desc,
-
- (struct usb_descriptor_header *)&as_out_interface_alt_0_desc,
- (struct usb_descriptor_header *)&as_out_interface_alt_1_desc,
- (struct usb_descriptor_header *)&as_out_header_desc,
-
- (struct usb_descriptor_header *)&as_out_type_i_desc,
-
- (struct usb_descriptor_header *)&hs_as_out_ep_desc,
- (struct usb_descriptor_header *)&as_iso_out_desc,
- (struct usb_descriptor_header *)&hs_as_in_fback_desc,
-
- (struct usb_descriptor_header *)&as_in_interface_alt_0_desc,
- (struct usb_descriptor_header *)&as_in_interface_alt_1_desc,
- (struct usb_descriptor_header *)&as_in_header_desc,
-
- (struct usb_descriptor_header *)&as_in_type_i_desc,
-
- (struct usb_descriptor_header *)&hs_as_in_ep_desc,
- (struct usb_descriptor_header *)&as_iso_in_desc,
- NULL,
-};
-
-/* Standard ISO OUT Endpoint Descriptor */
-static struct usb_endpoint_descriptor ss_as_out_ep_desc = {
- .bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
- .bEndpointAddress = USB_DIR_OUT,
- .bmAttributes = USB_ENDPOINT_SYNC_ADAPTIVE
- | USB_ENDPOINT_XFER_ISOC,
- .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
- .bInterval = 4,
-};
-
-static struct usb_ss_ep_comp_descriptor ss_as_out_ep_desc_comp = {
- .bLength = sizeof(ss_as_out_ep_desc_comp),
- .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
- .bMaxBurst = 0,
- .bmAttributes = 0,
- /* wBytesPerInterval = DYNAMIC */
-};
-
-/* Standard ISO IN Endpoint Descriptor */
-static struct usb_endpoint_descriptor ss_as_in_ep_desc = {
- .bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
- .bEndpointAddress = USB_DIR_IN,
- .bmAttributes = USB_ENDPOINT_SYNC_ASYNC
- | USB_ENDPOINT_XFER_ISOC,
- .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
- .bInterval = 4,
-};
-
-static struct usb_ss_ep_comp_descriptor ss_as_in_ep_desc_comp = {
- .bLength = sizeof(ss_as_in_ep_desc_comp),
- .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
- .bMaxBurst = 0,
- .bmAttributes = 0,
- /* wBytesPerInterval = DYNAMIC */
-};
-
-static struct usb_descriptor_header *f_audio_ss_desc[] = {
- (struct usb_descriptor_header *)&ac_interface_desc,
- (struct usb_descriptor_header *)&ac_header_desc,
-
- (struct usb_descriptor_header *)&usb_out_it_desc,
- (struct usb_descriptor_header *)&io_out_ot_desc,
- (struct usb_descriptor_header *)&out_feature_unit_desc,
-
- (struct usb_descriptor_header *)&io_in_it_desc,
- (struct usb_descriptor_header *)&usb_in_ot_desc,
- (struct usb_descriptor_header *)&in_feature_unit_desc,
-
- (struct usb_descriptor_header *)&ss_ac_int_ep_desc,
- (struct usb_descriptor_header *)&ss_ac_int_ep_desc_comp,
-
- (struct usb_descriptor_header *)&as_out_interface_alt_0_desc,
- (struct usb_descriptor_header *)&as_out_interface_alt_1_desc,
- (struct usb_descriptor_header *)&as_out_header_desc,
-
- (struct usb_descriptor_header *)&as_out_type_i_desc,
-
- (struct usb_descriptor_header *)&ss_as_out_ep_desc,
- (struct usb_descriptor_header *)&ss_as_out_ep_desc_comp,
- (struct usb_descriptor_header *)&as_iso_out_desc,
- (struct usb_descriptor_header *)&ss_as_in_fback_desc,
- (struct usb_descriptor_header *)&ss_as_in_fback_desc_comp,
-
- (struct usb_descriptor_header *)&as_in_interface_alt_0_desc,
- (struct usb_descriptor_header *)&as_in_interface_alt_1_desc,
- (struct usb_descriptor_header *)&as_in_header_desc,
-
- (struct usb_descriptor_header *)&as_in_type_i_desc,
-
- (struct usb_descriptor_header *)&ss_as_in_ep_desc,
- (struct usb_descriptor_header *)&ss_as_in_ep_desc_comp,
- (struct usb_descriptor_header *)&as_iso_in_desc,
- NULL,
-};
-
/*
* This function is an ALSA sound card following USB Audio Class Spec 1.0.
*/
@@ -602,11 +310,11 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
u8 control_selector = w_value >> 8;
int value = -EOPNOTSUPP;
- if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) {
+ if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
+ (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
unsigned int is_playback = 0;
- if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID))
+ if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
is_playback = 1;
if (control_selector == UAC_FU_MUTE) {
@@ -653,11 +361,11 @@ in_rq_min(struct usb_function *fn, const struct usb_ctrlrequest *cr)
u8 control_selector = w_value >> 8;
int value = -EOPNOTSUPP;
- if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) {
+ if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
+ (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
unsigned int is_playback = 0;
- if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID))
+ if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
is_playback = 1;
if (control_selector == UAC_FU_VOLUME) {
@@ -700,11 +408,11 @@ in_rq_max(struct usb_function *fn, const struct usb_ctrlrequest *cr)
u8 control_selector = w_value >> 8;
int value = -EOPNOTSUPP;
- if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) {
+ if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
+ (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
unsigned int is_playback = 0;
- if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID))
+ if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
is_playback = 1;
if (control_selector == UAC_FU_VOLUME) {
@@ -747,11 +455,11 @@ in_rq_res(struct usb_function *fn, const struct usb_ctrlrequest *cr)
u8 control_selector = w_value >> 8;
int value = -EOPNOTSUPP;
- if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) {
+ if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
+ (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
unsigned int is_playback = 0;
- if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID))
+ if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
is_playback = 1;
if (control_selector == UAC_FU_VOLUME) {
@@ -799,11 +507,11 @@ out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req)
return;
}
- if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) {
+ if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
+ (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
unsigned int is_playback = 0;
- if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID))
+ if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
is_playback = 1;
if (control_selector == UAC_FU_MUTE) {
@@ -848,8 +556,8 @@ out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
u8 entity_id = (w_index >> 8) & 0xff;
u8 control_selector = w_value >> 8;
- if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) {
+ if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
+ (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
memcpy(&uac1->setup_cr, cr, sizeof(*cr));
req->context = audio;
req->complete = out_rq_cur_complete;
@@ -1221,6 +929,10 @@ static struct usb_string *attach_strings(struct usb_composite_dev *cdev,
/*-------------------------------------------------------------------------*/
+/*
+ * Descriptor building functions
+ */
+
static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac1_alt_opts *alt_opts,
struct usb_endpoint_descriptor *ep_desc,
enum usb_device_speed speed, bool is_playback)
@@ -1231,11 +943,59 @@ static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac1_a
alt_opts->ssize, alt_opts->sync, alt_opts->c.opts->fb_max);
}
-static struct uac_feature_unit_descriptor *build_fu_desc(int chmask)
+struct path_params {
+ int dir;
+ int id;
+ struct f_uac1_opts *opts;
+ struct usb_string *strings;
+};
+
+/* 4.3.2.1 Input Terminal Descriptor */
+static void init_it_desc(struct uac_input_terminal_descriptor *it_desc,
+ struct f_uac1_alt_opts *alt_opts,
+ struct path_params *params)
+{
+ it_desc->bLength = UAC_DT_INPUT_TERMINAL_SIZE;
+ it_desc->bDescriptorType = USB_DT_CS_INTERFACE;
+ it_desc->bDescriptorSubtype = UAC_INPUT_TERMINAL;
+ it_desc->bTerminalID = params->id++;
+ it_desc->wTerminalType = cpu_to_le16((params->dir == HOST_TO_DEVICE)
+ ? UAC_TERMINAL_STREAMING
+ : alt_opts->terminal_type);
+ it_desc->bAssocTerminal = 0;
+ it_desc->bNrChannels = num_channels(alt_opts->chmask);
+ it_desc->wChannelConfig = cpu_to_le16(alt_opts->chmask);
+ it_desc->iTerminal = add_string(params->strings, alt_opts->it_name);
+ it_desc->iChannelNames = add_string(params->strings, alt_opts->it_ch_name);
+}
+
+/* 4.3.2.2 Output Terminal Descriptor */
+static void init_ot_desc(struct uac1_output_terminal_descriptor *ot_desc,
+ struct f_uac1_alt_opts *alt_opts,
+ struct path_params *params, int src_id)
+{
+ ot_desc->bLength = UAC_DT_OUTPUT_TERMINAL_SIZE;
+ ot_desc->bDescriptorType = USB_DT_CS_INTERFACE;
+ ot_desc->bDescriptorSubtype = UAC_OUTPUT_TERMINAL;
+ ot_desc->bTerminalID = params->id++;
+ ot_desc->wTerminalType = cpu_to_le16((params->dir == HOST_TO_DEVICE)
+ ? alt_opts->terminal_type
+ : UAC_TERMINAL_STREAMING);
+ ot_desc->bAssocTerminal = 0;
+ ot_desc->bSourceID = src_id;
+ ot_desc->iTerminal = add_string(params->strings, alt_opts->ot_name);
+}
+
+/* 4.3.2.5 Feature Unit Descriptor */
+static struct uac_feature_unit_descriptor *build_fu_desc(struct f_uac1_alt_opts *alt_opts,
+ struct path_params *params,
+ int src_id)
{
struct uac_feature_unit_descriptor *fu_desc;
- int channels = num_channels(chmask);
+ int channels = num_channels(alt_opts->chmask);
int fu_desc_size = UAC_DT_FEATURE_UNIT_SIZE(channels);
+ u32 control = 0;
+ u8 *i_feature;
fu_desc = kzalloc(fu_desc_size, GFP_KERNEL);
if (!fu_desc)
@@ -1243,29 +1003,46 @@ static struct uac_feature_unit_descriptor *build_fu_desc(int chmask)
fu_desc->bLength = fu_desc_size;
fu_desc->bDescriptorType = USB_DT_CS_INTERFACE;
-
fu_desc->bDescriptorSubtype = UAC_FEATURE_UNIT;
+ fu_desc->bUnitID = params->id++;
+ fu_desc->bSourceID = src_id;
fu_desc->bControlSize = 2;
- /* bUnitID, bSourceID and bmaControls will be defined later */
+ if (params->dir == HOST_TO_DEVICE) {
+ if (params->opts->c_mute_present)
+ control |= UAC_FU_MUTE;
+ if (params->opts->c_volume_present)
+ control |= UAC_FU_VOLUME;
+ }
+
+ if (params->dir == DEVICE_TO_HOST) {
+ if (params->opts->p_mute_present)
+ control |= UAC_FU_MUTE;
+ if (params->opts->p_volume_present)
+ control |= UAC_FU_VOLUME;
+ }
+
+ /* Only master volume/mute is supported. Per-channel controls are all zero. */
+ fu_desc->bmaControls[0] = cpu_to_le16(control);
+
+ /* iFeature is located after all channel controls */
+ i_feature = (u8 *)fu_desc + fu_desc->bLength - 1;
+ *i_feature = add_string(params->strings, alt_opts->fu_vol_name);
return fu_desc;
}
-/* B.3.2 Class-Specific AC Interface Descriptor */
+/* 4.3.2 Class-Specific AC Interface Descriptor */
static struct
-uac1_ac_header_descriptor *build_ac_header_desc(struct f_uac1_opts *opts)
+uac1_ac_header_descriptor *build_ac_header_desc(struct f_uac1 *uac1, struct f_uac1_opts *opts)
{
struct uac1_ac_header_descriptor *ac_desc;
int ac_header_desc_size;
- int num_ifaces = 0;
-
- if (EPOUT_EN(opts))
- num_ifaces++;
- if (EPIN_EN(opts))
- num_ifaces++;
+ int capture = epout_en_any(opts);
+ int playback = epin_en_any(opts);
+ int ba_iface_id = 0;
- ac_header_desc_size = UAC_DT_AC_HEADER_SIZE(num_ifaces);
+ ac_header_desc_size = UAC_DT_AC_HEADER_SIZE(capture + playback);
ac_desc = kzalloc(ac_header_desc_size, GFP_KERNEL);
if (!ac_desc)
@@ -1275,195 +1052,530 @@ uac1_ac_header_descriptor *build_ac_header_desc(struct f_uac1_opts *opts)
ac_desc->bDescriptorType = USB_DT_CS_INTERFACE;
ac_desc->bDescriptorSubtype = UAC_HEADER;
ac_desc->bcdADC = cpu_to_le16(0x0100);
- ac_desc->bInCollection = num_ifaces;
+ ac_desc->bInCollection = capture + playback;
+
+ if (capture)
+ ac_desc->baInterfaceNr[ba_iface_id++] = uac1->as_out_intf;
+
+ if (playback)
+ ac_desc->baInterfaceNr[ba_iface_id++] = uac1->as_in_intf;
- /* wTotalLength and baInterfaceNr will be defined later */
+ /* wTotalLength will be defined later */
return ac_desc;
}
+/* 4.5.1 Standard AS Interface Descriptor */
+static void init_as_interface_desc(struct usb_interface_descriptor *iface_desc,
+ u8 ifnum, u8 alt, u8 endpoints, const char *name,
+ struct usb_string *strings)
+{
+ iface_desc->bLength = USB_DT_INTERFACE_SIZE;
+ iface_desc->bDescriptorType = USB_DT_INTERFACE;
+ iface_desc->bInterfaceNumber = ifnum;
+ iface_desc->bAlternateSetting = alt;
+ iface_desc->bNumEndpoints = endpoints;
+ iface_desc->bInterfaceClass = USB_CLASS_AUDIO;
+ iface_desc->bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING;
+ iface_desc->bInterfaceProtocol = 0;
+ iface_desc->iInterface = add_string(strings, name);
+}
+
+/* 4.5.2 Class-Specific AS Interface Descriptor */
+static void init_as_header_desc(struct uac1_as_header_descriptor *as_header_desc, int terminalId)
+{
+ as_header_desc->bLength = UAC_DT_AS_HEADER_SIZE;
+ as_header_desc->bDescriptorType = USB_DT_CS_INTERFACE;
+ as_header_desc->bDescriptorSubtype = UAC_AS_GENERAL;
+ as_header_desc->bTerminalLink = terminalId;
+ as_header_desc->bDelay = 1;
+ as_header_desc->wFormatTag = cpu_to_le16(UAC_FORMAT_TYPE_I_PCM);
+}
+
+/* 4.5.3 Class-Specific AS Format Type Descriptor */
+static void init_uac_format_type_i_discrete_desc(struct f_uac1_alt_opts *alt_opts)
+{
+ int idx, i;
+
+ alt_opts->fmt_desc.bDescriptorType = USB_DT_CS_INTERFACE;
+ alt_opts->fmt_desc.bDescriptorSubtype = UAC_FORMAT_TYPE;
+ alt_opts->fmt_desc.bFormatType = UAC_FORMAT_TYPE_I;
+ alt_opts->fmt_desc.bNrChannels = num_channels(alt_opts->chmask);
+ alt_opts->fmt_desc.bSubframeSize = alt_opts->ssize;
+ alt_opts->fmt_desc.bBitResolution = alt_opts->ssize * 8;
+
+ /* Set sample rates */
+ for (i = 0, idx = 0; i < UAC_MAX_RATES; i++) {
+ if (alt_opts->srates[i] == 0)
+ break;
+ memcpy(alt_opts->fmt_desc.tSamFreq[idx++],
+ &alt_opts->srates[i], 3);
+ }
+ alt_opts->fmt_desc.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(idx);
+ alt_opts->fmt_desc.bSamFreqType = idx;
+}
+
+static int init_isoc_ep_descriptor(struct device *dev, struct usb_endpoint_descriptor *ep_desc,
+ struct f_uac1_alt_opts *alt_opts, int dir,
+ enum usb_device_speed speed, u8 addr)
+{
+ ep_desc->bLength = USB_DT_ENDPOINT_AUDIO_SIZE;
+ ep_desc->bDescriptorType = USB_DT_ENDPOINT;
+ ep_desc->bEndpointAddress = addr;
+ ep_desc->bmAttributes = USB_ENDPOINT_XFER_ISOC |
+ (((dir == HOST_TO_DEVICE) && !EPOUT_FBACK_IN_EN(alt_opts))
+ ? USB_ENDPOINT_SYNC_ADAPTIVE
+ : USB_ENDPOINT_SYNC_ASYNC);
+ ep_desc->bInterval = 1; /* For FS. For HS/SS, this is set later from hs_bint. */
+ ep_desc->bRefresh = 0;
+ ep_desc->bSynchAddress = EPOUT_FBACK_IN_EN(alt_opts)
+ ? fs_as_in_fback_desc.bEndpointAddress : 0;
+
+ return set_ep_max_packet_size_bint(dev, alt_opts, ep_desc, speed, (dir == DEVICE_TO_HOST));
+}
+
+static void init_isoc_ep_descriptor_comp(struct usb_ss_ep_comp_descriptor *ep_desc_comp,
+ struct usb_endpoint_descriptor *ep_desc)
+{
+ ep_desc_comp->bLength = sizeof(*ep_desc_comp),
+ ep_desc_comp->bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+ ep_desc_comp->bMaxBurst = 0,
+ ep_desc_comp->bmAttributes = 0,
+ ep_desc_comp->wBytesPerInterval = ep_desc->wMaxPacketSize;
+}
+
+static int init_alt_descriptors(struct device *dev, struct f_uac1_alt_opts *alt_opts, int ifnum,
+ u8 epaddr, int endpoints, int terminalID, int dir,
+ struct usb_string *strings)
+{
+ int status = 0;
+
+ init_as_header_desc(&alt_opts->as_header_desc, terminalID);
+ init_as_interface_desc(&alt_opts->intf_desc, ifnum, alt_opts->c.alt_num, endpoints,
+ alt_opts->name, strings);
+ init_uac_format_type_i_discrete_desc(alt_opts);
+
+ status = init_isoc_ep_descriptor(dev, &alt_opts->fs_iso_ep_desc, alt_opts, dir,
+ USB_SPEED_FULL, epaddr);
+ if (!status)
+ status = init_isoc_ep_descriptor(dev, &alt_opts->hs_iso_ep_desc, alt_opts, dir,
+ USB_SPEED_HIGH, epaddr);
+ if (!status)
+ status = init_isoc_ep_descriptor(dev, &alt_opts->ss_iso_ep_desc, alt_opts, dir,
+ USB_SPEED_SUPER, epaddr);
+
+ init_isoc_ep_descriptor_comp(&alt_opts->ss_iso_ep_desc_comp, &alt_opts->ss_iso_ep_desc);
+
+ return status;
+}
+
+static struct f_uac1_path_descriptors *
+build_path_descriptors(struct path_params *params, struct f_uac1_alt_opts *alt_opts)
+{
+ struct f_uac1_path_descriptors *path_descs;
+ u8 srcId;
+
+ path_descs = kzalloc(sizeof(*path_descs), GFP_KERNEL);
+ if (!path_descs)
+ return NULL;
+
+ path_descs->dir = params->dir;
+ path_descs->alt_opts = alt_opts;
+
+ init_it_desc(&path_descs->it_desc, alt_opts, params);
+ srcId = path_descs->it_desc.bTerminalID;
+
+ if (((params->dir == HOST_TO_DEVICE) && FUOUT_EN(params->opts)) ||
+ ((params->dir == DEVICE_TO_HOST) && FUIN_EN(params->opts))) {
+ path_descs->fu_desc = build_fu_desc(alt_opts, params,
+ path_descs->it_desc.bTerminalID);
+ srcId = path_descs->fu_desc->bUnitID;
+ }
+
+ init_ot_desc(&path_descs->ot_desc, alt_opts, params, srcId);
+
+ return path_descs;
+}
+
+static void free_path_descriptors(struct f_uac1_path_descriptors *path_descs)
+{
+ kfree(path_descs->fu_desc);
+ kfree(path_descs);
+}
+
+static struct f_uac1_path_descriptors *find_path_descriptors(struct list_head *list,
+ struct f_uac1_alt_opts *alt_opts,
+ int dir)
+{
+ struct f_uac1_path_descriptors *path_descs;
+
+ list_for_each_entry(path_descs, list, list) {
+ /* Check that all options used in the path descriptors are the same */
+ if ((path_descs->dir == dir) &&
+ (!strncmp(path_descs->alt_opts->name, alt_opts->name,
+ sizeof(alt_opts->name))) &&
+ (!strncmp(path_descs->alt_opts->it_name, alt_opts->it_name,
+ sizeof(alt_opts->it_name))) &&
+ (!strncmp(path_descs->alt_opts->it_ch_name, alt_opts->it_ch_name,
+ sizeof(alt_opts->it_ch_name))) &&
+ (!strncmp(path_descs->alt_opts->ot_name, alt_opts->ot_name,
+ sizeof(alt_opts->ot_name))) &&
+ (path_descs->alt_opts->chmask == alt_opts->chmask) &&
+ (path_descs->alt_opts->terminal_type == alt_opts->terminal_type))
+ return path_descs;
+ }
+ return NULL;
+}
+
+static int add_path_descriptors(struct list_head *list,
+ struct path_params *params,
+ struct f_uac1_alt_opts *alt_opts)
+{
+ int len = 0;
+ struct f_uac1_path_descriptors *path_descs;
+
+ if (!EP_EN(alt_opts))
+ return 0;
+
+ path_descs = find_path_descriptors(list, alt_opts, params->dir);
+
+ if (!path_descs) {
+ path_descs = build_path_descriptors(params, alt_opts);
+ if (path_descs) {
+ list_add_tail(&path_descs->list, list);
+ len += sizeof(path_descs->it_desc);
+ len += sizeof(path_descs->ot_desc);
+ if (path_descs->fu_desc)
+ len += path_descs->fu_desc->bLength;
+ }
+ }
+
+ if (path_descs) {
+ alt_opts->as_header_desc.bTerminalLink =
+ (params->dir == HOST_TO_DEVICE) ? path_descs->it_desc.bTerminalID
+ : path_descs->ot_desc.bTerminalID;
+ alt_opts->it_id = path_descs->it_desc.bTerminalID;
+ alt_opts->fu_id = path_descs->fu_desc ? path_descs->fu_desc->bUnitID : 0;
+ alt_opts->ot_id = path_descs->ot_desc.bTerminalID;
+ }
+
+ return len;
+}
+
/* Use macro to overcome line length limitation */
#define USBDHDR(p) (struct usb_descriptor_header *)(p)
-static void setup_headers(struct f_uac1_opts *opts,
- struct usb_descriptor_header **headers,
- enum usb_device_speed speed);
+static int setup_headers(struct usb_descriptor_header **desc_list,
+ struct f_uac1 *uac1,
+ struct f_uac1_opts *opts,
+ struct list_head *path_descs,
+ enum usb_device_speed speed);
-static void setup_descriptor(struct f_uac1_opts *opts)
+static int setup_descriptor(struct device *dev, struct f_uac1 *uac1, struct f_uac1_opts *opts,
+ struct usb_string *strings)
{
+ int status;
+ struct usb_descriptor_header **fs_desc_list, **hs_desc_list, **ss_ssp_desc_list;
+
/* patch descriptors */
- int i = 1; /* ID's start with 1 */
-
- if (EPOUT_EN(opts))
- usb_out_it_desc.bTerminalID = i++;
- if (EPIN_EN(opts))
- io_in_it_desc.bTerminalID = i++;
- if (EPOUT_EN(opts))
- io_out_ot_desc.bTerminalID = i++;
- if (EPIN_EN(opts))
- usb_in_ot_desc.bTerminalID = i++;
- if (FUOUT_EN(opts))
- out_feature_unit_desc->bUnitID = i++;
- if (FUIN_EN(opts))
- in_feature_unit_desc->bUnitID = i++;
-
- if (FUIN_EN(opts)) {
- usb_in_ot_desc.bSourceID = in_feature_unit_desc->bUnitID;
- in_feature_unit_desc->bSourceID = io_in_it_desc.bTerminalID;
- } else {
- usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID;
- }
- if (FUOUT_EN(opts)) {
- io_out_ot_desc.bSourceID = out_feature_unit_desc->bUnitID;
- out_feature_unit_desc->bSourceID = usb_out_it_desc.bTerminalID;
- } else {
- io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID;
+ int len;
+ struct list_head path_descs = LIST_HEAD_INIT(path_descs);
+ int fs_num, hs_num, ss_ssp_num;
+ struct f_uac1_alt_opts *alt_opts;
+ struct list_head *path_desc, *tmp;
+ struct path_params params;
+
+ params.id = 1; /* ID's start with 1 */
+ params.opts = opts;
+ params.strings = strings;
+
+ ac_header_desc = build_ac_header_desc(uac1, opts);
+ if (!ac_header_desc)
+ return -ENOMEM;
+
+ len = ac_header_desc->bLength;
+
+ if (uac1->g_audio.out_ep) {
+ params.dir = HOST_TO_DEVICE;
+ init_as_interface_desc(&opts->c_alt_0_opts.intf_desc, uac1->as_out_intf, 0, 0,
+ opts->c_alt_0_opts.name, strings);
+
+ /* Audio path descriptors (input terminal -> <feature unit> -> output terminal) */
+ len += add_path_descriptors(&path_descs, ¶ms, &opts->c_alt_1_opts);
+
+ status = init_alt_descriptors(dev, &opts->c_alt_1_opts, uac1->as_out_intf,
+ uac1->g_audio.out_ep->address,
+ EPOUT_FBACK_IN_EN(&opts->c_alt_1_opts) ? 2 : 1,
+ opts->c_alt_1_opts.it_id, HOST_TO_DEVICE, strings);
+ if (status) {
+ dev_err(dev, "Failed to init alt descs for capture alt %d (%d)\n",
+ 1, status);
+ goto cleanup;
+ }
+
+ list_for_each_entry(alt_opts, &opts->c_alt_opts, list) {
+ len += add_path_descriptors(&path_descs, ¶ms, alt_opts);
+
+ status = init_alt_descriptors(dev, alt_opts, uac1->as_out_intf,
+ uac1->g_audio.out_ep->address,
+ EPOUT_FBACK_IN_EN(alt_opts) ? 2 : 1,
+ alt_opts->it_id, HOST_TO_DEVICE, strings);
+ if (status) {
+ dev_err(dev, "Failed to init alt descs for capture alt %d (%d)\n",
+ alt_opts->c.alt_num, status);
+ goto cleanup;
+ }
+ }
}
- as_out_header_desc.bTerminalLink = usb_out_it_desc.bTerminalID;
- as_in_header_desc.bTerminalLink = usb_in_ot_desc.bTerminalID;
+ if (uac1->g_audio.in_ep) {
+ params.dir = DEVICE_TO_HOST;
+ init_as_interface_desc(&opts->p_alt_0_opts.intf_desc, uac1->as_in_intf, 0, 0,
+ opts->p_alt_0_opts.name, strings);
- io_in_it_desc.wTerminalType = cpu_to_le16(opts->c_terminal_type);
- io_out_ot_desc.wTerminalType = cpu_to_le16(opts->p_terminal_type);
- ac_header_desc->wTotalLength = cpu_to_le16(ac_header_desc->bLength);
+ /* Audio path descriptors (input terminal -> <feature unit> -> output terminal) */
+ len += add_path_descriptors(&path_descs, ¶ms, &opts->p_alt_1_opts);
- if (EPIN_EN(opts)) {
- u16 len = le16_to_cpu(ac_header_desc->wTotalLength);
+ status = init_alt_descriptors(dev, &opts->p_alt_1_opts, uac1->as_in_intf,
+ uac1->g_audio.in_ep->address, 1,
+ opts->p_alt_1_opts.ot_id, DEVICE_TO_HOST, strings);
+ if (status) {
+ dev_err(dev, "Failed to init alt descs for playback alt %d (%d)\n",
+ 1, status);
+ goto cleanup;
+ }
- len += sizeof(usb_in_ot_desc);
- len += sizeof(io_in_it_desc);
- if (FUIN_EN(opts))
- len += in_feature_unit_desc->bLength;
- ac_header_desc->wTotalLength = cpu_to_le16(len);
+ list_for_each_entry(alt_opts, &opts->p_alt_opts, list) {
+ len += add_path_descriptors(&path_descs, ¶ms, alt_opts);
+
+ status = init_alt_descriptors(dev, alt_opts, uac1->as_in_intf,
+ uac1->g_audio.in_ep->address, 1,
+ alt_opts->ot_id, DEVICE_TO_HOST, strings);
+ if (status) {
+ dev_err(dev, "Failed to init alt descs for playback alt %d (%d)\n",
+ alt_opts->c.alt_num, status);
+ goto cleanup;
+ }
+ }
}
- if (EPOUT_EN(opts)) {
- u16 len = le16_to_cpu(ac_header_desc->wTotalLength);
- len += sizeof(usb_out_it_desc);
- len += sizeof(io_out_ot_desc);
- if (FUOUT_EN(opts))
- len += out_feature_unit_desc->bLength;
- ac_header_desc->wTotalLength = cpu_to_le16(len);
+ ac_header_desc->wTotalLength = cpu_to_le16(len);
+
+ /* Count how many descriptors we have and then allocate and populate */
+ fs_num = setup_headers(NULL, uac1, opts, &path_descs, USB_SPEED_FULL);
+ hs_num = setup_headers(NULL, uac1, opts, &path_descs, USB_SPEED_HIGH);
+ ss_ssp_num = setup_headers(NULL, uac1, opts, &path_descs, USB_SPEED_SUPER);
+
+ fs_desc_list = kzalloc((fs_num + hs_num + ss_ssp_num) * sizeof(*fs_desc_list), GFP_KERNEL);
+ if (!fs_desc_list) {
+ status = -ENOMEM;
+ goto cleanup;
}
+ hs_desc_list = fs_desc_list + fs_num;
+ ss_ssp_desc_list = hs_desc_list + hs_num;
- setup_headers(opts, f_audio_fs_desc, USB_SPEED_FULL);
- setup_headers(opts, f_audio_hs_desc, USB_SPEED_HIGH);
- setup_headers(opts, f_audio_ss_desc, USB_SPEED_SUPER);
+ (void) setup_headers(fs_desc_list, uac1, opts, &path_descs, USB_SPEED_FULL);
+ (void) setup_headers(hs_desc_list, uac1, opts, &path_descs, USB_SPEED_HIGH);
+ (void) setup_headers(ss_ssp_desc_list, uac1, opts, &path_descs, USB_SPEED_SUPER);
+
+ /* copy descriptors, and track endpoint copies */
+ status = usb_assign_descriptors(&uac1->g_audio.func, fs_desc_list, hs_desc_list,
+ ss_ssp_desc_list, ss_ssp_desc_list);
+
+ if (status)
+ dev_err(dev, "Failed to assign descriptors (%d)\n", status);
+
+ kfree(fs_desc_list);
+
+cleanup:
+ list_for_each_safe(path_desc, tmp, &path_descs) {
+ free_path_descriptors(
+ container_of(path_desc, struct f_uac1_path_descriptors, list));
+ }
+ kfree(ac_header_desc);
+ ac_header_desc = NULL;
+
+ return status;
}
-static void setup_headers(struct f_uac1_opts *opts,
- struct usb_descriptor_header **headers,
- enum usb_device_speed speed)
+static inline void add_descriptor(int i, struct usb_descriptor_header **desc_list,
+ struct usb_descriptor_header *desc)
+{
+ if (desc_list)
+ desc_list[i] = desc;
+}
+
+static int add_alt_descriptors(int i, struct usb_descriptor_header **desc_list,
+ struct f_uac1_alt_opts *alt_opts, enum usb_device_speed speed)
+{
+ add_descriptor(i++, desc_list, USBDHDR(&alt_opts->intf_desc));
+ add_descriptor(i++, desc_list, USBDHDR(&alt_opts->as_header_desc));
+ add_descriptor(i++, desc_list, USBDHDR(&alt_opts->fmt_desc));
+ if (speed == USB_SPEED_FULL)
+ add_descriptor(i++, desc_list, USBDHDR(&alt_opts->fs_iso_ep_desc));
+ else if (speed == USB_SPEED_HIGH)
+ add_descriptor(i++, desc_list, USBDHDR(&alt_opts->hs_iso_ep_desc));
+ else if (speed == USB_SPEED_SUPER || speed == USB_SPEED_SUPER_PLUS) {
+ add_descriptor(i++, desc_list, USBDHDR(&alt_opts->ss_iso_ep_desc));
+ add_descriptor(i++, desc_list, USBDHDR(&alt_opts->ss_iso_ep_desc_comp));
+ }
+
+ return i;
+}
+
+static int setup_headers(struct usb_descriptor_header **desc_list,
+ struct f_uac1 *uac1,
+ struct f_uac1_opts *opts,
+ struct list_head *path_descs,
+ enum usb_device_speed speed)
{
- struct usb_ss_ep_comp_descriptor *epout_desc_comp = NULL;
- struct usb_ss_ep_comp_descriptor *epin_desc_comp = NULL;
struct usb_ss_ep_comp_descriptor *epin_fback_desc_comp = NULL;
struct usb_ss_ep_comp_descriptor *ep_int_desc_comp = NULL;
- struct usb_endpoint_descriptor *epout_desc;
- struct usb_endpoint_descriptor *epin_desc;
struct usb_endpoint_descriptor *epin_fback_desc;
struct usb_endpoint_descriptor *ep_int_desc;
- int i;
+
+ int i = 0;
+ struct list_head *pos;
switch (speed) {
case USB_SPEED_FULL:
- epout_desc = &fs_as_out_ep_desc;
- epin_desc = &fs_as_in_ep_desc;
epin_fback_desc = &fs_as_in_fback_desc;
ep_int_desc = &fs_ac_int_ep_desc;
break;
case USB_SPEED_HIGH:
- epout_desc = &hs_as_out_ep_desc;
- epin_desc = &hs_as_in_ep_desc;
epin_fback_desc = &hs_as_in_fback_desc;
ep_int_desc = &hs_ac_int_ep_desc;
break;
default:
- epout_desc = &ss_as_out_ep_desc;
- epin_desc = &ss_as_in_ep_desc;
- epout_desc_comp = &ss_as_out_ep_desc_comp;
- epin_desc_comp = &ss_as_in_ep_desc_comp;
epin_fback_desc = &ss_as_in_fback_desc;
epin_fback_desc_comp = &ss_as_in_fback_desc_comp;
ep_int_desc = &ss_ac_int_ep_desc;
ep_int_desc_comp = &ss_ac_int_ep_desc_comp;
}
- i = 0;
- headers[i++] = USBDHDR(&ac_interface_desc);
- headers[i++] = USBDHDR(ac_header_desc);
+ add_descriptor(i++, desc_list, USBDHDR(&ac_interface_desc));
+ add_descriptor(i++, desc_list, USBDHDR(ac_header_desc));
- if (EPOUT_EN(opts)) {
- headers[i++] = USBDHDR(&usb_out_it_desc);
- headers[i++] = USBDHDR(&io_out_ot_desc);
- if (FUOUT_EN(opts))
- headers[i++] = USBDHDR(out_feature_unit_desc);
- }
-
- if (EPIN_EN(opts)) {
- headers[i++] = USBDHDR(&io_in_it_desc);
- headers[i++] = USBDHDR(&usb_in_ot_desc);
- if (FUIN_EN(opts))
- headers[i++] = USBDHDR(in_feature_unit_desc);
+ list_for_each(pos, path_descs) {
+ struct f_uac1_path_descriptors *path_desc =
+ container_of(pos, struct f_uac1_path_descriptors, list);
+ add_descriptor(i++, desc_list, USBDHDR(&path_desc->it_desc));
+ add_descriptor(i++, desc_list, USBDHDR(&path_desc->ot_desc));
+ if (path_desc->fu_desc)
+ add_descriptor(i++, desc_list, USBDHDR(path_desc->fu_desc));
}
+ // If any FU exists, add the interrupt endpoint descriptor
if (FUOUT_EN(opts) || FUIN_EN(opts)) {
- headers[i++] = USBDHDR(ep_int_desc);
+ add_descriptor(i++, desc_list, USBDHDR(ep_int_desc));
if (ep_int_desc_comp)
- headers[i++] = USBDHDR(ep_int_desc_comp);
+ add_descriptor(i++, desc_list, USBDHDR(ep_int_desc_comp));
}
- if (EPOUT_EN(opts)) {
- headers[i++] = USBDHDR(&as_out_interface_alt_0_desc);
- headers[i++] = USBDHDR(&as_out_interface_alt_1_desc);
- headers[i++] = USBDHDR(&as_out_header_desc);
- headers[i++] = USBDHDR(&as_out_type_i_desc);
- headers[i++] = USBDHDR(epout_desc);
- if (epout_desc_comp)
- headers[i++] = USBDHDR(epout_desc_comp);
+ // If any capture interface is active
+ if (epout_en_any(opts)) {
+ struct f_uac1_alt_opts *alt_opts;
+
+ add_descriptor(i++, desc_list, USBDHDR(&opts->c_alt_0_opts.intf_desc));
- headers[i++] = USBDHDR(&as_iso_out_desc);
+ if (EP_EN(&opts->c_alt_1_opts)) {
+ i = add_alt_descriptors(i, desc_list, &opts->c_alt_1_opts, speed);
- if (EPOUT_FBACK_IN_EN(opts)) {
- headers[i++] = USBDHDR(epin_fback_desc);
- if (epin_fback_desc_comp)
- headers[i++] = USBDHDR(epin_fback_desc_comp);
+ add_descriptor(i++, desc_list, USBDHDR(&as_iso_out_desc));
+ if (EPOUT_FBACK_IN_EN(&opts->c_alt_1_opts)) {
+ add_descriptor(i++, desc_list, USBDHDR(epin_fback_desc));
+ if (epin_fback_desc_comp)
+ add_descriptor(i++, desc_list,
+ USBDHDR(epin_fback_desc_comp));
+ }
+ }
+
+ list_for_each_entry(alt_opts, &opts->c_alt_opts, list) {
+ if (EP_EN(alt_opts)) {
+ i = add_alt_descriptors(i, desc_list, alt_opts, speed);
+
+ add_descriptor(i++, desc_list, USBDHDR(&as_iso_out_desc));
+ if (EPOUT_FBACK_IN_EN(alt_opts)) {
+ add_descriptor(i++, desc_list, USBDHDR(epin_fback_desc));
+ if (epin_fback_desc_comp)
+ add_descriptor(i++, desc_list,
+ USBDHDR(epin_fback_desc_comp));
+ }
+ }
}
}
- if (EPIN_EN(opts)) {
- headers[i++] = USBDHDR(&as_in_interface_alt_0_desc);
- headers[i++] = USBDHDR(&as_in_interface_alt_1_desc);
- headers[i++] = USBDHDR(&as_in_header_desc);
- headers[i++] = USBDHDR(&as_in_type_i_desc);
- headers[i++] = USBDHDR(epin_desc);
- if (epin_desc_comp)
- headers[i++] = USBDHDR(epin_desc_comp);
- headers[i++] = USBDHDR(&as_iso_in_desc);
+ // If any playback interface is active
+ if (epin_en_any(opts)) {
+ struct f_uac1_alt_opts *alt_opts;
+
+ add_descriptor(i++, desc_list, USBDHDR(&opts->p_alt_0_opts.intf_desc));
+
+ if (EP_EN(&opts->p_alt_1_opts)) {
+ i = add_alt_descriptors(i, desc_list, &opts->p_alt_1_opts, speed);
+
+ add_descriptor(i++, desc_list, USBDHDR(&as_iso_in_desc));
+ }
+
+ list_for_each_entry(alt_opts, &opts->p_alt_opts, list) {
+ if (EP_EN(alt_opts)) {
+ i = add_alt_descriptors(i, desc_list, alt_opts, speed);
+
+ add_descriptor(i++, desc_list, USBDHDR(&as_iso_in_desc));
+ }
+ }
}
- headers[i] = NULL;
+
+ add_descriptor(i++, desc_list, NULL);
+
+ return i;
}
static int f_audio_validate_opts(struct g_audio *audio, struct device *dev)
{
struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio);
+ struct f_uac1_alt_opts *alt_opts;
- if (!opts->p_chmask && !opts->c_chmask) {
+ if (!epin_en_any(opts) && !epout_en_any(opts)) {
dev_err(dev, "Error: no playback and capture channels\n");
return -EINVAL;
- } else if (opts->p_chmask & ~UAC1_CHANNEL_MASK) {
- dev_err(dev, "Error: unsupported playback channels mask\n");
- return -EINVAL;
- } else if (opts->c_chmask & ~UAC1_CHANNEL_MASK) {
- dev_err(dev, "Error: unsupported capture channels mask\n");
- return -EINVAL;
- } else if ((opts->p_ssize < 1) || (opts->p_ssize > 4)) {
- dev_err(dev, "Error: incorrect playback sample size\n");
- return -EINVAL;
- } else if ((opts->c_ssize < 1) || (opts->c_ssize > 4)) {
- dev_err(dev, "Error: incorrect capture sample size\n");
- return -EINVAL;
- } else if (!opts->p_srates[0]) {
+ }
+
+ list_for_each_entry(alt_opts, &opts->p_alt_opts, list) {
+ if (alt_opts->chmask & ~UAC1_CHANNEL_MASK) {
+ dev_err(dev, "Error: unsupported playback channels mask for alt %d\n",
+ alt_opts->c.alt_num);
+ return -EINVAL;
+ } else if ((alt_opts->ssize < 1) || (alt_opts->ssize > 4)) {
+ dev_err(dev, "Error: incorrect playback sample size for alt %d\n",
+ alt_opts->c.alt_num);
+ return -EINVAL;
+ } else if ((alt_opts->hs_bint < 0) || (alt_opts->hs_bint > 4)) {
+ dev_err(dev, "Error: incorrect playback HS/SS bInterval (1-4: fixed, 0: auto) for alt %d\n",
+ alt_opts->c.alt_num);
+
+ return -EINVAL;
+ }
+ }
+
+ list_for_each_entry(alt_opts, &opts->c_alt_opts, list) {
+ if (alt_opts->chmask & ~UAC1_CHANNEL_MASK) {
+ dev_err(dev, "Error: unsupported capture channels mask for alt %d\n",
+ alt_opts->c.alt_num);
+ return -EINVAL;
+ } else if ((alt_opts->ssize < 1) || (alt_opts->ssize > 4)) {
+ dev_err(dev, "Error: incorrect capture sample size for alt %d\n",
+ alt_opts->c.alt_num);
+ return -EINVAL;
+ } else if ((alt_opts->hs_bint < 0) || (alt_opts->hs_bint > 4)) {
+ dev_err(dev, "Error: incorrect capture HS/SS bInterval (1-4: fixed, 0: auto) for alt %d\n",
+ alt_opts->c.alt_num);
+
+ return -EINVAL;
+ }
+ }
+
+ if (!opts->p_srates[0]) {
dev_err(dev, "Error: incorrect playback sampling rate\n");
return -EINVAL;
} else if (!opts->c_srates[0]) {
@@ -1493,15 +1605,6 @@ static int f_audio_validate_opts(struct g_audio *audio, struct device *dev)
return -EINVAL;
}
- if ((opts->p_hs_bint < 0) || (opts->p_hs_bint > 4)) {
- dev_err(dev, "Error: incorrect playback HS/SS bInterval (1-4: fixed, 0: auto)\n");
- return -EINVAL;
- }
- if ((opts->c_hs_bint < 0) || (opts->c_hs_bint > 4)) {
- dev_err(dev, "Error: incorrect capture HS/SS bInterval (1-4: fixed, 0: auto)\n");
- return -EINVAL;
- }
-
return 0;
}
@@ -1551,6 +1654,26 @@ static void init_alt_opts(struct f_uac1_alt_opts *alt_opts, struct f_uac1_opts *
alt_opts->terminal_type = (playback) ? opts->p_terminal_type : opts->c_terminal_type;
}
+static u16 get_max_packet_size(struct f_uac1_alt_opts *alt_opts, struct list_head *list)
+{
+ u16 max_psize = max_t(u16,
+ le16_to_cpu(alt_opts->fs_iso_ep_desc.wMaxPacketSize),
+ le16_to_cpu(alt_opts->hs_iso_ep_desc.wMaxPacketSize));
+ max_psize = max_t(u16, max_psize,
+ le16_to_cpu(alt_opts->ss_iso_ep_desc.wMaxPacketSize));
+
+ list_for_each_entry(alt_opts, list, list) {
+ max_psize = max_t(u16, max_psize,
+ le16_to_cpu(alt_opts->fs_iso_ep_desc.wMaxPacketSize));
+ max_psize = max_t(u16, max_psize,
+ le16_to_cpu(alt_opts->hs_iso_ep_desc.wMaxPacketSize));
+ max_psize = max_t(u16, max_psize,
+ le16_to_cpu(alt_opts->ss_iso_ep_desc.wMaxPacketSize));
+ }
+
+ return max_psize;
+}
+
/* audio function driver setup/binding */
static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
{
@@ -1562,9 +1685,8 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
struct f_uac1_opts *audio_opts;
struct usb_ep *ep = NULL;
struct usb_string *us;
- int ba_iface_id;
int status;
- int idx, i;
+ struct list_head strings = LIST_HEAD_INIT(strings);
audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst);
@@ -1588,214 +1710,41 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
* be used from their alt mode opts.
*/
- us = attach_strings(cdev, audio_opts);
- if (IS_ERR(us))
- return PTR_ERR(us);
-
- ac_header_desc = build_ac_header_desc(audio_opts);
- if (!ac_header_desc)
- return -ENOMEM;
-
- if (FUOUT_EN(audio_opts)) {
- out_feature_unit_desc = build_fu_desc(audio_opts->c_chmask);
- if (!out_feature_unit_desc) {
- status = -ENOMEM;
- goto fail;
- }
- }
- if (FUIN_EN(audio_opts)) {
- in_feature_unit_desc = build_fu_desc(audio_opts->p_chmask);
- if (!in_feature_unit_desc) {
- status = -ENOMEM;
- goto err_free_fu;
- }
- }
-
- ac_interface_desc.iInterface = add_string(us, audio_opts->function_name);
- usb_out_it_desc.iTerminal = add_string(us, audio_opts->c_alt_1_opts.it_name);
- usb_out_it_desc.iChannelNames = add_string(us, audio_opts->c_alt_1_opts.it_ch_name);
- io_out_ot_desc.iTerminal = add_string(us, audio_opts->c_alt_1_opts.ot_name);
- as_out_interface_alt_0_desc.iInterface = add_string(us, audio_opts->c_alt_0_opts.name);
- as_out_interface_alt_1_desc.iInterface = add_string(us, audio_opts->c_alt_1_opts.name);
- io_in_it_desc.iTerminal = add_string(us, audio_opts->p_alt_1_opts.it_name);
- io_in_it_desc.iChannelNames = add_string(us, audio_opts->p_alt_1_opts.it_ch_name);
- usb_in_ot_desc.iTerminal = add_string(us, audio_opts->p_alt_1_opts.ot_name);
- as_in_interface_alt_0_desc.iInterface = add_string(us, audio_opts->p_alt_0_opts.name);
- as_in_interface_alt_1_desc.iInterface = add_string(us, audio_opts->p_alt_1_opts.name);
-
- if (FUOUT_EN(audio_opts)) {
- u8 *i_feature;
-
- i_feature = (u8 *)out_feature_unit_desc +
- out_feature_unit_desc->bLength - 1;
- *i_feature = add_string(us, audio_opts->c_alt_1_opts.fu_vol_name);
- }
- if (FUIN_EN(audio_opts)) {
- u8 *i_feature;
-
- i_feature = (u8 *)in_feature_unit_desc +
- in_feature_unit_desc->bLength - 1;
- *i_feature = add_string(us, audio_opts->p_alt_1_opts.fu_vol_name);
- }
-
us = attach_strings(cdev, audio_opts);
if (IS_ERR(us)) {
status = PTR_ERR(us);
goto fail;
}
- /* Set channel numbers */
- usb_out_it_desc.bNrChannels = num_channels(audio_opts->c_chmask);
- usb_out_it_desc.wChannelConfig = cpu_to_le16(audio_opts->c_chmask);
- as_out_type_i_desc.bNrChannels = num_channels(audio_opts->c_chmask);
- as_out_type_i_desc.bSubframeSize = audio_opts->c_ssize;
- as_out_type_i_desc.bBitResolution = audio_opts->c_ssize * 8;
- io_in_it_desc.bNrChannels = num_channels(audio_opts->p_chmask);
- io_in_it_desc.wChannelConfig = cpu_to_le16(audio_opts->p_chmask);
- as_in_type_i_desc.bNrChannels = num_channels(audio_opts->p_chmask);
- as_in_type_i_desc.bSubframeSize = audio_opts->p_ssize;
- as_in_type_i_desc.bBitResolution = audio_opts->p_ssize * 8;
-
- if (FUOUT_EN(audio_opts)) {
- __le16 *bma = (__le16 *)&out_feature_unit_desc->bmaControls[0];
- u32 control = 0;
-
- if (audio_opts->c_mute_present)
- control |= UAC_FU_MUTE;
- if (audio_opts->c_volume_present)
- control |= UAC_FU_VOLUME;
- *bma = cpu_to_le16(control);
- }
- if (FUIN_EN(audio_opts)) {
- __le16 *bma = (__le16 *)&in_feature_unit_desc->bmaControls[0];
- u32 control = 0;
-
- if (audio_opts->p_mute_present)
- control |= UAC_FU_MUTE;
- if (audio_opts->p_volume_present)
- control |= UAC_FU_VOLUME;
- *bma = cpu_to_le16(control);
- }
-
- /* Set sample rates */
- for (i = 0, idx = 0; i < UAC_MAX_RATES; i++) {
- if (audio_opts->c_srates[i] == 0)
- break;
- memcpy(as_out_type_i_desc.tSamFreq[idx++],
- &audio_opts->c_srates[i], 3);
- }
- as_out_type_i_desc.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(idx);
- as_out_type_i_desc.bSamFreqType = idx;
+ ac_interface_desc.iInterface = add_string(us, audio_opts->function_name);
- for (i = 0, idx = 0; i < UAC_MAX_RATES; i++) {
- if (audio_opts->p_srates[i] == 0)
- break;
- memcpy(as_in_type_i_desc.tSamFreq[idx++],
- &audio_opts->p_srates[i], 3);
- }
- as_in_type_i_desc.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(idx);
- as_in_type_i_desc.bSamFreqType = idx;
uac1->p_srate = audio_opts->p_srates[0];
uac1->c_srate = audio_opts->c_srates[0];
- /* allocate instance-specific interface IDs, and patch descriptors */
+ /* allocate instance-specific interface IDs */
status = usb_interface_id(c, f);
if (status < 0)
- goto err_free_fu;
+ goto fail;
ac_interface_desc.bInterfaceNumber = status;
- ac_interface_desc.bNumEndpoints = 1;
uac1->ac_intf = status;
uac1->ac_alt = 0;
- ba_iface_id = 0;
-
- if (EPOUT_EN(audio_opts)) {
+ if (epout_en_any(audio_opts)) {
status = usb_interface_id(c, f);
if (status < 0)
- goto err_free_fu;
- as_out_interface_alt_0_desc.bInterfaceNumber = status;
- as_out_interface_alt_1_desc.bInterfaceNumber = status;
- ac_header_desc->baInterfaceNr[ba_iface_id++] = status;
+ goto fail;
uac1->as_out_intf = status;
uac1->as_out_alt = 0;
-
- if (EPOUT_FBACK_IN_EN(audio_opts)) {
- fs_as_out_ep_desc.bmAttributes =
- USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC;
- hs_as_out_ep_desc.bmAttributes =
- USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC;
- ss_as_out_ep_desc.bmAttributes =
- USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC;
- ac_interface_desc.bNumEndpoints++;
- } else {
- fs_as_out_ep_desc.bmAttributes =
- USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE;
- hs_as_out_ep_desc.bmAttributes =
- USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE;
- ss_as_out_ep_desc.bmAttributes =
- USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE;
- }
}
- if (EPIN_EN(audio_opts)) {
+ if (epin_en_any(audio_opts)) {
status = usb_interface_id(c, f);
if (status < 0)
- goto err_free_fu;
- as_in_interface_alt_0_desc.bInterfaceNumber = status;
- as_in_interface_alt_1_desc.bInterfaceNumber = status;
- ac_header_desc->baInterfaceNr[ba_iface_id++] = status;
+ goto fail;
uac1->as_in_intf = status;
uac1->as_in_alt = 0;
}
- hs_as_in_ep_desc.bInterval = audio_opts->p_hs_bint;
- ss_as_in_ep_desc.bInterval = audio_opts->p_hs_bint;
- hs_as_out_ep_desc.bInterval = audio_opts->c_hs_bint;
- ss_as_out_ep_desc.bInterval = audio_opts->c_hs_bint;
-
- /* Calculate wMaxPacketSize according to audio bandwidth */
- status = set_ep_max_packet_size_bint(dev, &audio_opts->p_alt_1_opts, &fs_as_in_ep_desc,
- USB_SPEED_FULL, true);
- if (status < 0) {
- dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- goto err_free_fu;
- }
-
- status = set_ep_max_packet_size_bint(dev, &audio_opts->c_alt_1_opts, &fs_as_out_ep_desc,
- USB_SPEED_FULL, false);
- if (status < 0) {
- dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- goto err_free_fu;
- }
-
- status = set_ep_max_packet_size_bint(dev, &audio_opts->p_alt_1_opts, &hs_as_in_ep_desc,
- USB_SPEED_HIGH, true);
- if (status < 0) {
- dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- goto err_free_fu;
- }
-
- status = set_ep_max_packet_size_bint(dev, &audio_opts->c_alt_1_opts, &hs_as_out_ep_desc,
- USB_SPEED_HIGH, false);
- if (status < 0) {
- dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- goto err_free_fu;
- }
-
- status = set_ep_max_packet_size_bint(dev, &audio_opts->p_alt_1_opts, &ss_as_in_ep_desc,
- USB_SPEED_SUPER, true);
- if (status < 0) {
- dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- goto err_free_fu;
- }
-
- status = set_ep_max_packet_size_bint(dev, &audio_opts->c_alt_1_opts, &hs_as_out_ep_desc,
- USB_SPEED_SUPER, false);
- if (status < 0) {
- dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- goto err_free_fu;
- }
-
audio->gadget = gadget;
status = -ENODEV;
@@ -1805,36 +1754,49 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
/* allocate AC interrupt endpoint */
if (FUOUT_EN(audio_opts) || FUIN_EN(audio_opts)) {
ep = usb_ep_autoconfig(cdev->gadget, &fs_ac_int_ep_desc);
- if (!ep)
- goto err_free_fu;
-
+ if (!ep) {
+ status = -EINVAL;
+ dev_err(dev, "Failed to allocate interrupt endpoint\n");
+ goto fail;
+ }
hs_ac_int_ep_desc.bEndpointAddress = fs_ac_int_ep_desc.bEndpointAddress;
ss_ac_int_ep_desc.bEndpointAddress = fs_ac_int_ep_desc.bEndpointAddress;
uac1->int_ep = ep;
- uac1->int_ep->desc = &fs_ac_int_ep_desc;
ac_interface_desc.bNumEndpoints = 1;
}
- /* allocate instance-specific endpoints */
- if (EPOUT_EN(audio_opts)) {
- ep = usb_ep_autoconfig(cdev->gadget, &fs_as_out_ep_desc);
- if (!ep)
- goto err_free_fu;
-
- hs_as_out_ep_desc.bEndpointAddress = fs_as_out_ep_desc.bEndpointAddress;
- ss_as_out_ep_desc.bEndpointAddress = fs_as_out_ep_desc.bEndpointAddress;
- ss_as_out_ep_desc_comp.wBytesPerInterval = ss_as_out_ep_desc.wMaxPacketSize;
+ /* Allocate instance-specific endpoints. These use the FS version for alt mode 1.
+ * All other alt modes and speeds will be initialized to the same endpoint address
+ * during the setup_descriptor() call. The u_audio code will update the currently
+ * selected endpoint descriptor when the alt mode changes.
+ */
+ if (epout_en_any(audio_opts)) {
+ status = init_isoc_ep_descriptor(dev, &audio_opts->c_alt_1_opts.fs_iso_ep_desc,
+ &audio_opts->c_alt_1_opts, HOST_TO_DEVICE,
+ USB_SPEED_FULL, USB_DIR_OUT);
+ if (status) {
+ dev_err(dev, "Failed to init FS isoc ep descriptor for capture (%d)\n",
+ status);
+ goto fail;
+ }
+ ep = usb_ep_autoconfig(cdev->gadget, &audio_opts->c_alt_1_opts.fs_iso_ep_desc);
+ if (!ep) {
+ status = -EINVAL;
+ dev_err(dev, "Failed to allocate isoc endpoint for capture\n");
+ goto fail;
+ }
audio->out_ep = ep;
- audio->out_ep->desc = &fs_as_out_ep_desc;
- if (EPOUT_FBACK_IN_EN(audio_opts)) {
+ if (epout_fback_in_en_any(audio_opts)) {
ep = usb_ep_autoconfig(cdev->gadget, &fs_as_in_fback_desc);
- if (!ep)
- goto err_free_fu;
-
+ if (!ep) {
+ status = -EINVAL;
+ dev_err(dev, "Failed to allocate feedback endpoint for capture\n");
+ goto fail;
+ }
hs_as_in_fback_desc.bEndpointAddress = fs_as_in_fback_desc.bEndpointAddress;
ss_as_in_fback_desc.bEndpointAddress = fs_as_in_fback_desc.bEndpointAddress;
@@ -1842,45 +1804,44 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
}
}
- if (EPIN_EN(audio_opts)) {
- ep = usb_ep_autoconfig(cdev->gadget, &fs_as_in_ep_desc);
- if (!ep)
- goto err_free_fu;
-
- hs_as_in_ep_desc.bEndpointAddress = fs_as_in_ep_desc.bEndpointAddress;
- ss_as_in_ep_desc.bEndpointAddress = fs_as_in_ep_desc.bEndpointAddress;
- ss_as_in_ep_desc_comp.wBytesPerInterval = ss_as_in_ep_desc.wMaxPacketSize;
+ if (epin_en_any(audio_opts)) {
+ status = init_isoc_ep_descriptor(dev, &audio_opts->p_alt_1_opts.fs_iso_ep_desc,
+ &audio_opts->p_alt_1_opts, HOST_TO_DEVICE,
+ USB_SPEED_FULL, USB_DIR_IN);
+ if (status) {
+ dev_err(dev, "Failed to init FS isoc ep descriptor for playback (%d)\n",
+ status);
+ goto fail;
+ }
+ ep = usb_ep_autoconfig(cdev->gadget, &audio_opts->p_alt_1_opts.fs_iso_ep_desc);
+ if (!ep) {
+ status = -EINVAL;
+ dev_err(dev, "Failed to allocate isoc endpoint for playback\n");
+ goto fail;
+ }
audio->in_ep = ep;
- audio->in_ep->desc = &fs_as_in_ep_desc;
}
- setup_descriptor(audio_opts);
+ status = setup_descriptor(dev, uac1, audio_opts, us);
- /* copy descriptors, and track endpoint copies */
- status = usb_assign_descriptors(f, f_audio_fs_desc, f_audio_hs_desc, f_audio_ss_desc,
- f_audio_ss_desc);
if (status)
- goto err_free_fu;
-
- audio->in_ep_maxpsize = max_t(u16,
- le16_to_cpu(fs_as_in_ep_desc.wMaxPacketSize),
- le16_to_cpu(hs_as_in_ep_desc.wMaxPacketSize));
- audio->out_ep_maxpsize = max_t(u16,
- le16_to_cpu(fs_as_out_ep_desc.wMaxPacketSize),
- le16_to_cpu(hs_as_out_ep_desc.wMaxPacketSize));
+ goto fail;
- audio->in_ep_maxpsize = max_t(u16, audio->in_ep_maxpsize,
- le16_to_cpu(ss_as_in_ep_desc.wMaxPacketSize));
- audio->out_ep_maxpsize = max_t(u16, audio->out_ep_maxpsize,
- le16_to_cpu(ss_as_out_ep_desc.wMaxPacketSize));
+ // Set max packet size for all alt modes. These are used to allocate the buffers in u_audio.
+ audio->out_ep_maxpsize = get_max_packet_size(&audio_opts->c_alt_1_opts,
+ &audio_opts->c_alt_opts);
+ audio->in_ep_maxpsize = get_max_packet_size(&audio_opts->p_alt_1_opts,
+ &audio_opts->p_alt_opts);
+ // TODO: This may need some change with the audio params for the current alt mode
audio->params.c_chmask = audio_opts->c_chmask;
memcpy(audio->params.c_srates, audio_opts->c_srates,
sizeof(audio->params.c_srates));
audio->params.c_ssize = audio_opts->c_ssize;
+
if (FUIN_EN(audio_opts)) {
- audio->params.p_fu.id = USB_IN_FU_ID;
+ audio->params.p_fu.id = USB_IN_FU_ID(audio_opts);
audio->params.p_fu.mute_present = audio_opts->p_mute_present;
audio->params.p_fu.volume_present =
audio_opts->p_volume_present;
@@ -1888,12 +1849,15 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
audio->params.p_fu.volume_max = audio_opts->p_volume_max;
audio->params.p_fu.volume_res = audio_opts->p_volume_res;
}
+
+ // TODO: This may need some change with the audio params for the current alt mode
audio->params.p_chmask = audio_opts->p_chmask;
memcpy(audio->params.p_srates, audio_opts->p_srates,
sizeof(audio->params.p_srates));
audio->params.p_ssize = audio_opts->p_ssize;
+
if (FUOUT_EN(audio_opts)) {
- audio->params.c_fu.id = USB_OUT_FU_ID;
+ audio->params.c_fu.id = USB_OUT_FU_ID(audio_opts);
audio->params.c_fu.mute_present = audio_opts->c_mute_present;
audio->params.c_fu.volume_present =
audio_opts->c_volume_present;
@@ -1914,14 +1878,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
err_card_register:
usb_free_all_descriptors(f);
-err_free_fu:
- kfree(out_feature_unit_desc);
- out_feature_unit_desc = NULL;
- kfree(in_feature_unit_desc);
- in_feature_unit_desc = NULL;
fail:
- kfree(ac_header_desc);
- ac_header_desc = NULL;
return status;
}
@@ -2342,11 +2299,6 @@ static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f)
g_audio_cleanup(audio);
usb_free_all_descriptors(f);
- kfree(out_feature_unit_desc);
- out_feature_unit_desc = NULL;
- kfree(in_feature_unit_desc);
- in_feature_unit_desc = NULL;
-
kfree(ac_header_desc);
ac_header_desc = NULL;
diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h
index ae69f1eb872d..e45bd17eb92f 100644
--- a/drivers/usb/gadget/function/u_uac1.h
+++ b/drivers/usb/gadget/function/u_uac1.h
@@ -9,6 +9,7 @@
#define __U_UAC1_H
#include <linux/usb/composite.h>
+#include <linux/usb/audio.h>
#include "uac_common.h"
#define UAC1_OUT_EP_MAX_PACKET_SIZE 200
@@ -47,8 +48,15 @@ struct f_uac1_alt_0_opts {
struct f_uac1_alt_opts_common c;
char name[USB_MAX_STRING_LEN];
+
+ /* Descriptors */
+ struct usb_interface_descriptor intf_desc;
};
+DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(UAC_MAX_RATES);
+#define uac_format_type_i_discrete_descriptor \
+ uac_format_type_i_discrete_descriptor_##UAC_MAX_RATES
+
/* Alt modes 1+ */
struct f_uac1_alt_opts {
struct f_uac1_alt_opts_common c;
@@ -70,6 +78,36 @@ struct f_uac1_alt_opts {
u8 hs_bint;
s16 terminal_type;
+ /* Descriptors */
+ struct usb_interface_descriptor intf_desc;
+ struct uac1_as_header_descriptor as_header_desc;
+ struct uac_format_type_i_discrete_descriptor fmt_desc;
+
+ struct usb_endpoint_descriptor fs_iso_ep_desc;
+ struct usb_endpoint_descriptor hs_iso_ep_desc;
+ struct usb_endpoint_descriptor ss_iso_ep_desc;
+ struct usb_ss_ep_comp_descriptor ss_iso_ep_desc_comp;
+
+ u8 it_id; /* Input Terminal Descriptor bTerminalID */
+ u8 fu_id; /* Feature Unit Descriptor bUnitID */
+ u8 ot_id; /* Output Terminal Descriptor bTerminalID */
+};
+
+#undef uac_format_type_i_discrete_descriptor
+
+struct f_uac1_path_descriptors {
+ struct list_head list;
+
+ int dir; /* HOST_TO_DEVICE or DEVICE_TO_HOST */
+
+ /* Alt mode opts this path descriptor is from */
+ struct f_uac1_alt_opts *alt_opts;
+
+ struct uac_input_terminal_descriptor it_desc;
+ struct uac1_output_terminal_descriptor ot_desc;
+
+ /* Feature unit is optional */
+ struct uac_feature_unit_descriptor *fu_desc;
};
struct f_uac1_opts {
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH RFC 12/14] usb: gadget: f_uac2: Generate dynamic descriptors based on alt opts
2024-09-28 15:08 [PATCH RFC 00/14] usb: gadget: f_uac: Add support for alt mode settings crwulff
` (10 preceding siblings ...)
2024-09-28 15:09 ` [PATCH RFC 11/14] usb: gadget: f_uac1: Generate dynamic descriptors based on alt opts crwulff
@ 2024-09-28 15:09 ` crwulff
2024-09-28 15:09 ` [PATCH RFC 13/14] usb: gadget: f_uac1: support ganged volume/mute controls crwulff
2024-09-28 15:09 ` [PATCH RFC 14/14] usb: gadget: f_uac2: " crwulff
13 siblings, 0 replies; 15+ messages in thread
From: crwulff @ 2024-09-28 15:09 UTC (permalink / raw)
To: linux-usb
Cc: Pavel Hofman, Greg Kroah-Hartman, James Gruber, Jeff Johnson,
John Keeping, Jonathan Corbet, Lee Jones, Perr Zhang, linux-doc,
linux-kernel, Chris Wulff
From: Chris Wulff <crwulff@gmail.com>
Descriptors are now generated based on what alt modes are configured.
The lifetime of allocations has changed a bit with this such that we
deallocate our copy of the descriptors as soon as they've been registered.
Many of the descriptors that were static are now attached to their alt mode
opts and initialized with a function.
Signed-off-by: Chris Wulff <crwulff@gmail.com>
---
drivers/usb/gadget/function/f_uac2.c | 1322 ++++++++++++--------------
drivers/usb/gadget/function/u_uac2.h | 32 +
2 files changed, 654 insertions(+), 700 deletions(-)
diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index 54702888855d..c30fbd062793 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -35,8 +35,8 @@
*/
#define USB_OUT_CLK_ID (out_clk_src_desc.bClockID)
#define USB_IN_CLK_ID (in_clk_src_desc.bClockID)
-#define USB_OUT_FU_ID (out_feature_unit_desc->bUnitID)
-#define USB_IN_FU_ID (in_feature_unit_desc->bUnitID)
+#define USB_OUT_FU_ID(_opts) (_opts->c_alt_1_opts.fu_id)
+#define USB_IN_FU_ID(_opts) (_opts->p_alt_1_opts.fu_id)
#define CONTROL_ABSENT 0
#define CONTROL_RDONLY 1
@@ -54,15 +54,36 @@
#define UNFLW_CTRL 8
#define OVFLW_CTRL 10
-#define EPIN_EN(_opts) ((_opts)->p_chmask != 0)
-#define EPOUT_EN(_opts) ((_opts)->c_chmask != 0)
-#define FUIN_EN(_opts) (EPIN_EN(_opts) \
+
+#define EP_EN(_alt_opts) ((_alt_opts) && ((_alt_opts)->chmask != 0))
+#define FUIN_EN(_opts) (EP_EN(&_opts->p_alt_1_opts) \
&& ((_opts)->p_mute_present \
|| (_opts)->p_volume_present))
-#define FUOUT_EN(_opts) (EPOUT_EN(_opts) \
+#define FUOUT_EN(_opts) (EP_EN(&_opts->c_alt_1_opts) \
&& ((_opts)->c_mute_present \
|| (_opts)->c_volume_present))
-#define EPOUT_FBACK_IN_EN(_opts) ((_opts)->c_sync == USB_ENDPOINT_SYNC_ASYNC)
+#define EPOUT_FBACK_IN_EN(_alt_opts) ((_alt_opts)->sync == USB_ENDPOINT_SYNC_ASYNC)
+
+/* Check if any alt mode has option enabled */
+#define EN_ANY(single, fn, cp) \
+static int fn(struct f_uac2_opts *opts) \
+{ \
+ struct f_uac2_alt_opts *alt_opts; \
+ \
+ if (single(&opts->cp##_alt_1_opts)) \
+ return 1; \
+ \
+ list_for_each_entry(alt_opts, &opts->cp##_alt_opts, list) { \
+ if (single(alt_opts)) \
+ return 1; \
+ } \
+ \
+ return 0; \
+}
+
+EN_ANY(EP_EN, epout_en_any, c)
+EN_ANY(EP_EN, epin_en_any, p)
+EN_ANY(EPOUT_FBACK_IN_EN, epout_fback_in_en_any, p)
struct f_uac2 {
struct g_audio g_audio;
@@ -94,7 +115,7 @@ static int afunc_notify(struct g_audio *agdev, int unit_id, int cs);
/* --------- USB Function Interface ------------- */
static struct usb_interface_assoc_descriptor iad_desc = {
- .bLength = sizeof iad_desc,
+ .bLength = sizeof(iad_desc),
.bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
.bFirstInterface = 0,
@@ -140,65 +161,6 @@ static struct uac_clock_source_descriptor out_clk_src_desc = {
.bAssocTerminal = 0,
};
-/* Input Terminal for USB_OUT */
-static struct uac2_input_terminal_descriptor usb_out_it_desc = {
- .bLength = sizeof usb_out_it_desc,
- .bDescriptorType = USB_DT_CS_INTERFACE,
-
- .bDescriptorSubtype = UAC_INPUT_TERMINAL,
- /* .bTerminalID = DYNAMIC */
- .wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
- .bAssocTerminal = 0,
- /* .bCSourceID = DYNAMIC */
- .iChannelNames = 0,
- .bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL),
-};
-
-/* Input Terminal for I/O-In */
-static struct uac2_input_terminal_descriptor io_in_it_desc = {
- .bLength = sizeof io_in_it_desc,
- .bDescriptorType = USB_DT_CS_INTERFACE,
-
- .bDescriptorSubtype = UAC_INPUT_TERMINAL,
- /* .bTerminalID = DYNAMIC */
- /* .wTerminalType = DYNAMIC */
- .bAssocTerminal = 0,
- /* .bCSourceID = DYNAMIC */
- .iChannelNames = 0,
- .bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL),
-};
-
-/* Ouput Terminal for USB_IN */
-static struct uac2_output_terminal_descriptor usb_in_ot_desc = {
- .bLength = sizeof usb_in_ot_desc,
- .bDescriptorType = USB_DT_CS_INTERFACE,
-
- .bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
- /* .bTerminalID = DYNAMIC */
- .wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
- .bAssocTerminal = 0,
- /* .bSourceID = DYNAMIC */
- /* .bCSourceID = DYNAMIC */
- .bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL),
-};
-
-/* Ouput Terminal for I/O-Out */
-static struct uac2_output_terminal_descriptor io_out_ot_desc = {
- .bLength = sizeof io_out_ot_desc,
- .bDescriptorType = USB_DT_CS_INTERFACE,
-
- .bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
- /* .bTerminalID = DYNAMIC */
- /* .wTerminalType = DYNAMIC */
- .bAssocTerminal = 0,
- /* .bSourceID = DYNAMIC */
- /* .bCSourceID = DYNAMIC */
- .bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL),
-};
-
-static struct uac2_feature_unit_descriptor *in_feature_unit_desc;
-static struct uac2_feature_unit_descriptor *out_feature_unit_desc;
-
static struct uac2_ac_header_descriptor ac_hdr_desc = {
.bLength = sizeof ac_hdr_desc,
.bDescriptorType = USB_DT_CS_INTERFACE,
@@ -246,89 +208,6 @@ static struct usb_ss_ep_comp_descriptor ss_ep_int_desc_comp = {
.wBytesPerInterval = cpu_to_le16(6),
};
-/* Audio Streaming OUT Interface - Alt0 */
-static struct usb_interface_descriptor std_as_out_if0_desc = {
- .bLength = sizeof std_as_out_if0_desc,
- .bDescriptorType = USB_DT_INTERFACE,
-
- .bAlternateSetting = 0,
- .bNumEndpoints = 0,
- .bInterfaceClass = USB_CLASS_AUDIO,
- .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
- .bInterfaceProtocol = UAC_VERSION_2,
-};
-
-/* Audio Streaming OUT Interface - Alt1 */
-static struct usb_interface_descriptor std_as_out_if1_desc = {
- .bLength = sizeof std_as_out_if1_desc,
- .bDescriptorType = USB_DT_INTERFACE,
-
- .bAlternateSetting = 1,
- .bNumEndpoints = 1,
- .bInterfaceClass = USB_CLASS_AUDIO,
- .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
- .bInterfaceProtocol = UAC_VERSION_2,
-};
-
-/* Audio Stream OUT Intface Desc */
-static struct uac2_as_header_descriptor as_out_hdr_desc = {
- .bLength = sizeof as_out_hdr_desc,
- .bDescriptorType = USB_DT_CS_INTERFACE,
-
- .bDescriptorSubtype = UAC_AS_GENERAL,
- /* .bTerminalLink = DYNAMIC */
- .bmControls = 0,
- .bFormatType = UAC_FORMAT_TYPE_I,
- .bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM),
- .iChannelNames = 0,
-};
-
-/* Audio USB_OUT Format */
-static struct uac2_format_type_i_descriptor as_out_fmt1_desc = {
- .bLength = sizeof as_out_fmt1_desc,
- .bDescriptorType = USB_DT_CS_INTERFACE,
- .bDescriptorSubtype = UAC_FORMAT_TYPE,
- .bFormatType = UAC_FORMAT_TYPE_I,
-};
-
-/* STD AS ISO OUT Endpoint */
-static struct usb_endpoint_descriptor fs_epout_desc = {
- .bLength = USB_DT_ENDPOINT_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
-
- .bEndpointAddress = USB_DIR_OUT,
- /* .bmAttributes = DYNAMIC */
- /* .wMaxPacketSize = DYNAMIC */
- .bInterval = 1,
-};
-
-static struct usb_endpoint_descriptor hs_epout_desc = {
- .bLength = USB_DT_ENDPOINT_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
-
- /* .bmAttributes = DYNAMIC */
- /* .wMaxPacketSize = DYNAMIC */
- /* .bInterval = DYNAMIC */
-};
-
-static struct usb_endpoint_descriptor ss_epout_desc = {
- .bLength = USB_DT_ENDPOINT_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
-
- .bEndpointAddress = USB_DIR_OUT,
- /* .bmAttributes = DYNAMIC */
- /* .wMaxPacketSize = DYNAMIC */
- /* .bInterval = DYNAMIC */
-};
-
-static struct usb_ss_ep_comp_descriptor ss_epout_desc_comp = {
- .bLength = sizeof(ss_epout_desc_comp),
- .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
- .bMaxBurst = 0,
- .bmAttributes = 0,
- /* wBytesPerInterval = DYNAMIC */
-};
-
/* CS AS ISO OUT Endpoint */
static struct uac2_iso_endpoint_descriptor as_iso_out_desc = {
.bLength = sizeof as_iso_out_desc,
@@ -379,90 +258,6 @@ static struct usb_ss_ep_comp_descriptor ss_epin_fback_desc_comp = {
.wBytesPerInterval = cpu_to_le16(4),
};
-
-/* Audio Streaming IN Interface - Alt0 */
-static struct usb_interface_descriptor std_as_in_if0_desc = {
- .bLength = sizeof std_as_in_if0_desc,
- .bDescriptorType = USB_DT_INTERFACE,
-
- .bAlternateSetting = 0,
- .bNumEndpoints = 0,
- .bInterfaceClass = USB_CLASS_AUDIO,
- .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
- .bInterfaceProtocol = UAC_VERSION_2,
-};
-
-/* Audio Streaming IN Interface - Alt1 */
-static struct usb_interface_descriptor std_as_in_if1_desc = {
- .bLength = sizeof std_as_in_if1_desc,
- .bDescriptorType = USB_DT_INTERFACE,
-
- .bAlternateSetting = 1,
- .bNumEndpoints = 1,
- .bInterfaceClass = USB_CLASS_AUDIO,
- .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
- .bInterfaceProtocol = UAC_VERSION_2,
-};
-
-/* Audio Stream IN Intface Desc */
-static struct uac2_as_header_descriptor as_in_hdr_desc = {
- .bLength = sizeof as_in_hdr_desc,
- .bDescriptorType = USB_DT_CS_INTERFACE,
-
- .bDescriptorSubtype = UAC_AS_GENERAL,
- /* .bTerminalLink = DYNAMIC */
- .bmControls = 0,
- .bFormatType = UAC_FORMAT_TYPE_I,
- .bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM),
- .iChannelNames = 0,
-};
-
-/* Audio USB_IN Format */
-static struct uac2_format_type_i_descriptor as_in_fmt1_desc = {
- .bLength = sizeof as_in_fmt1_desc,
- .bDescriptorType = USB_DT_CS_INTERFACE,
- .bDescriptorSubtype = UAC_FORMAT_TYPE,
- .bFormatType = UAC_FORMAT_TYPE_I,
-};
-
-/* STD AS ISO IN Endpoint */
-static struct usb_endpoint_descriptor fs_epin_desc = {
- .bLength = USB_DT_ENDPOINT_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
-
- .bEndpointAddress = USB_DIR_IN,
- .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
- /* .wMaxPacketSize = DYNAMIC */
- .bInterval = 1,
-};
-
-static struct usb_endpoint_descriptor hs_epin_desc = {
- .bLength = USB_DT_ENDPOINT_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
-
- .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
- /* .wMaxPacketSize = DYNAMIC */
- /* .bInterval = DYNAMIC */
-};
-
-static struct usb_endpoint_descriptor ss_epin_desc = {
- .bLength = USB_DT_ENDPOINT_SIZE,
- .bDescriptorType = USB_DT_ENDPOINT,
-
- .bEndpointAddress = USB_DIR_IN,
- .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
- /* .wMaxPacketSize = DYNAMIC */
- /* .bInterval = DYNAMIC */
-};
-
-static struct usb_ss_ep_comp_descriptor ss_epin_desc_comp = {
- .bLength = sizeof(ss_epin_desc_comp),
- .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
- .bMaxBurst = 0,
- .bmAttributes = 0,
- /* wBytesPerInterval = DYNAMIC */
-};
-
/* CS AS ISO IN Endpoint */
static struct uac2_iso_endpoint_descriptor as_iso_in_desc = {
.bLength = sizeof as_iso_in_desc,
@@ -475,115 +270,6 @@ static struct uac2_iso_endpoint_descriptor as_iso_in_desc = {
.wLockDelay = 0,
};
-static struct usb_descriptor_header *fs_audio_desc[] = {
- (struct usb_descriptor_header *)&iad_desc,
- (struct usb_descriptor_header *)&std_ac_if_desc,
-
- (struct usb_descriptor_header *)&ac_hdr_desc,
- (struct usb_descriptor_header *)&in_clk_src_desc,
- (struct usb_descriptor_header *)&out_clk_src_desc,
- (struct usb_descriptor_header *)&usb_out_it_desc,
- (struct usb_descriptor_header *)&out_feature_unit_desc,
- (struct usb_descriptor_header *)&io_in_it_desc,
- (struct usb_descriptor_header *)&usb_in_ot_desc,
- (struct usb_descriptor_header *)&in_feature_unit_desc,
- (struct usb_descriptor_header *)&io_out_ot_desc,
-
- (struct usb_descriptor_header *)&fs_ep_int_desc,
-
- (struct usb_descriptor_header *)&std_as_out_if0_desc,
- (struct usb_descriptor_header *)&std_as_out_if1_desc,
-
- (struct usb_descriptor_header *)&as_out_hdr_desc,
- (struct usb_descriptor_header *)&as_out_fmt1_desc,
- (struct usb_descriptor_header *)&fs_epout_desc,
- (struct usb_descriptor_header *)&as_iso_out_desc,
- (struct usb_descriptor_header *)&fs_epin_fback_desc,
-
- (struct usb_descriptor_header *)&std_as_in_if0_desc,
- (struct usb_descriptor_header *)&std_as_in_if1_desc,
-
- (struct usb_descriptor_header *)&as_in_hdr_desc,
- (struct usb_descriptor_header *)&as_in_fmt1_desc,
- (struct usb_descriptor_header *)&fs_epin_desc,
- (struct usb_descriptor_header *)&as_iso_in_desc,
- NULL,
-};
-
-static struct usb_descriptor_header *hs_audio_desc[] = {
- (struct usb_descriptor_header *)&iad_desc,
- (struct usb_descriptor_header *)&std_ac_if_desc,
-
- (struct usb_descriptor_header *)&ac_hdr_desc,
- (struct usb_descriptor_header *)&in_clk_src_desc,
- (struct usb_descriptor_header *)&out_clk_src_desc,
- (struct usb_descriptor_header *)&usb_out_it_desc,
- (struct usb_descriptor_header *)&out_feature_unit_desc,
- (struct usb_descriptor_header *)&io_in_it_desc,
- (struct usb_descriptor_header *)&usb_in_ot_desc,
- (struct usb_descriptor_header *)&in_feature_unit_desc,
- (struct usb_descriptor_header *)&io_out_ot_desc,
-
- (struct usb_descriptor_header *)&hs_ep_int_desc,
-
- (struct usb_descriptor_header *)&std_as_out_if0_desc,
- (struct usb_descriptor_header *)&std_as_out_if1_desc,
-
- (struct usb_descriptor_header *)&as_out_hdr_desc,
- (struct usb_descriptor_header *)&as_out_fmt1_desc,
- (struct usb_descriptor_header *)&hs_epout_desc,
- (struct usb_descriptor_header *)&as_iso_out_desc,
- (struct usb_descriptor_header *)&hs_epin_fback_desc,
-
- (struct usb_descriptor_header *)&std_as_in_if0_desc,
- (struct usb_descriptor_header *)&std_as_in_if1_desc,
-
- (struct usb_descriptor_header *)&as_in_hdr_desc,
- (struct usb_descriptor_header *)&as_in_fmt1_desc,
- (struct usb_descriptor_header *)&hs_epin_desc,
- (struct usb_descriptor_header *)&as_iso_in_desc,
- NULL,
-};
-
-static struct usb_descriptor_header *ss_audio_desc[] = {
- (struct usb_descriptor_header *)&iad_desc,
- (struct usb_descriptor_header *)&std_ac_if_desc,
-
- (struct usb_descriptor_header *)&ac_hdr_desc,
- (struct usb_descriptor_header *)&in_clk_src_desc,
- (struct usb_descriptor_header *)&out_clk_src_desc,
- (struct usb_descriptor_header *)&usb_out_it_desc,
- (struct usb_descriptor_header *)&out_feature_unit_desc,
- (struct usb_descriptor_header *)&io_in_it_desc,
- (struct usb_descriptor_header *)&usb_in_ot_desc,
- (struct usb_descriptor_header *)&in_feature_unit_desc,
- (struct usb_descriptor_header *)&io_out_ot_desc,
-
- (struct usb_descriptor_header *)&ss_ep_int_desc,
- (struct usb_descriptor_header *)&ss_ep_int_desc_comp,
-
- (struct usb_descriptor_header *)&std_as_out_if0_desc,
- (struct usb_descriptor_header *)&std_as_out_if1_desc,
-
- (struct usb_descriptor_header *)&as_out_hdr_desc,
- (struct usb_descriptor_header *)&as_out_fmt1_desc,
- (struct usb_descriptor_header *)&ss_epout_desc,
- (struct usb_descriptor_header *)&ss_epout_desc_comp,
- (struct usb_descriptor_header *)&as_iso_out_desc,
- (struct usb_descriptor_header *)&ss_epin_fback_desc,
- (struct usb_descriptor_header *)&ss_epin_fback_desc_comp,
-
- (struct usb_descriptor_header *)&std_as_in_if0_desc,
- (struct usb_descriptor_header *)&std_as_in_if1_desc,
-
- (struct usb_descriptor_header *)&as_in_hdr_desc,
- (struct usb_descriptor_header *)&as_in_fmt1_desc,
- (struct usb_descriptor_header *)&ss_epin_desc,
- (struct usb_descriptor_header *)&ss_epin_desc_comp,
- (struct usb_descriptor_header *)&as_iso_in_desc,
- NULL,
-};
-
struct cntrl_cur_lay2 {
__le16 wCUR;
};
@@ -720,11 +406,65 @@ static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac2_a
alt_opts->ssize, alt_opts->sync, alt_opts->c.opts->fb_max);
}
-static struct uac2_feature_unit_descriptor *build_fu_desc(int chmask)
+struct path_params {
+ int dir;
+ int id;
+ struct f_uac2_opts *opts;
+ struct usb_string *strings;
+};
+
+/* Audio20 4.7.2.4 Input Terminal Descriptor */
+static void init_it_desc(struct uac2_input_terminal_descriptor *it_desc,
+ struct f_uac2_alt_opts *alt_opts,
+ struct path_params *params)
+{
+ it_desc->bLength = sizeof(*it_desc);
+ it_desc->bDescriptorType = USB_DT_CS_INTERFACE;
+ it_desc->bDescriptorSubtype = UAC_INPUT_TERMINAL;
+ it_desc->bTerminalID = params->id++;
+ it_desc->wTerminalType = cpu_to_le16((params->dir == HOST_TO_DEVICE) ?
+ UAC_TERMINAL_STREAMING :
+ alt_opts->terminal_type);
+ it_desc->bAssocTerminal = 0;
+ it_desc->bCSourceID = (params->dir == HOST_TO_DEVICE) ? out_clk_src_desc.bClockID
+ : in_clk_src_desc.bClockID;
+ it_desc->bNrChannels = num_channels(alt_opts->chmask);
+ it_desc->bmChannelConfig = cpu_to_le32(alt_opts->chmask);
+ it_desc->iChannelNames = add_string(params->strings, alt_opts->it_ch_name);
+ it_desc->bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL);
+ it_desc->iTerminal = add_string(params->strings, alt_opts->it_name);
+}
+
+/* Audio20 4.7.2.5 Output Terminal Descriptor */
+static void init_ot_desc(struct uac2_output_terminal_descriptor *ot_desc,
+ struct f_uac2_alt_opts *alt_opts,
+ struct path_params *params, int src_id)
+{
+ ot_desc->bLength = sizeof(*ot_desc);
+ ot_desc->bDescriptorType = USB_DT_CS_INTERFACE;
+ ot_desc->bDescriptorSubtype = UAC_OUTPUT_TERMINAL;
+ ot_desc->bTerminalID = params->id++;
+ ot_desc->wTerminalType = cpu_to_le16((params->dir == HOST_TO_DEVICE) ?
+ alt_opts->terminal_type :
+ UAC_TERMINAL_STREAMING);
+ ot_desc->bAssocTerminal = 0;
+ ot_desc->bSourceID = src_id;
+ ot_desc->bCSourceID = (params->dir == HOST_TO_DEVICE) ? out_clk_src_desc.bClockID
+ : in_clk_src_desc.bClockID;
+ ot_desc->bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL);
+ ot_desc->iTerminal = add_string(params->strings, alt_opts->ot_name);
+}
+
+/* Audio20 4.7.2.8 Feature Unit Descriptor */
+static struct uac2_feature_unit_descriptor *build_fu_desc(struct f_uac2_alt_opts *alt_opts,
+ struct path_params *params, int src_id)
{
struct uac2_feature_unit_descriptor *fu_desc;
- int channels = num_channels(chmask);
+ int channels = num_channels(alt_opts->chmask);
int fu_desc_size = UAC2_DT_FEATURE_UNIT_SIZE(channels);
+ __le32 *bma;
+ u32 control = 0;
+ u8 *i_feature;
fu_desc = kzalloc(fu_desc_size, GFP_KERNEL);
if (!fu_desc)
@@ -732,218 +472,520 @@ static struct uac2_feature_unit_descriptor *build_fu_desc(int chmask)
fu_desc->bLength = fu_desc_size;
fu_desc->bDescriptorType = USB_DT_CS_INTERFACE;
-
fu_desc->bDescriptorSubtype = UAC_FEATURE_UNIT;
+ fu_desc->bUnitID = params->id++;
+ fu_desc->bSourceID = src_id;
/* bUnitID, bSourceID and bmaControls will be defined later */
+ if (params->dir == HOST_TO_DEVICE) {
+ if (params->opts->c_mute_present)
+ control |= CONTROL_RDWR << FU_MUTE_CTRL;
+ if (params->opts->c_volume_present)
+ control |= CONTROL_RDWR << FU_VOL_CTRL;
+ }
+
+ if (params->dir == DEVICE_TO_HOST) {
+ if (params->opts->p_mute_present)
+ control |= CONTROL_RDWR << FU_MUTE_CTRL;
+ if (params->opts->p_volume_present)
+ control |= CONTROL_RDWR << FU_VOL_CTRL;
+ }
+
+ /* Only master volume/mute is supported. Per-channel controls are all zero. */
+ bma = (__le32 *)&fu_desc->bmaControls[0];
+ *bma = cpu_to_le32(control);
+
+ /* iFeature is located after all channel controls */
+ i_feature = (u8 *)fu_desc + fu_desc->bLength - 1;
+ *i_feature = add_string(params->strings, alt_opts->fu_vol_name);
return fu_desc;
}
+/* Audio20 4.9.1 Standard AS Interface Descriptor */
+static void init_as_interface_desc(struct usb_interface_descriptor *iface_desc,
+ u8 ifnum, u8 alt, u8 endpoints, const char *name,
+ struct usb_string *strings)
+{
+ iface_desc->bLength = sizeof(*iface_desc);
+ iface_desc->bDescriptorType = USB_DT_INTERFACE;
+ iface_desc->bInterfaceNumber = ifnum;
+ iface_desc->bAlternateSetting = alt;
+ iface_desc->bNumEndpoints = endpoints;
+ iface_desc->bInterfaceClass = USB_CLASS_AUDIO;
+ iface_desc->bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING;
+ iface_desc->bInterfaceProtocol = UAC_VERSION_2;
+ iface_desc->iInterface = add_string(strings, name);
+}
+
+/* Audio20 4.9.2 Class-Specific AS Interface Descriptor */
+static void init_as_header_desc(struct f_uac2_alt_opts *alt_opts, int terminalId)
+{
+ alt_opts->as_header_desc.bLength = sizeof(alt_opts->as_header_desc);
+ alt_opts->as_header_desc.bDescriptorType = USB_DT_CS_INTERFACE;
+ alt_opts->as_header_desc.bDescriptorSubtype = UAC_AS_GENERAL;
+ alt_opts->as_header_desc.bTerminalLink = terminalId;
+ alt_opts->as_header_desc.bmControls = 0;
+ alt_opts->as_header_desc.bFormatType = UAC_FORMAT_TYPE_I;
+ alt_opts->as_header_desc.bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM);
+ alt_opts->as_header_desc.bNrChannels = num_channels(alt_opts->chmask);
+ alt_opts->as_header_desc.bmChannelConfig = cpu_to_le32(alt_opts->chmask);
+ alt_opts->as_header_desc.iChannelNames = 0;
+}
+
+/* Audio20 4.9.3 Class-Specific AS Format Type Descriptor */
+static void init_uac_format_type_i_discrete_desc(struct f_uac2_alt_opts *alt_opts)
+{
+ alt_opts->fmt_desc.bLength = sizeof(alt_opts->fmt_desc);
+ alt_opts->fmt_desc.bDescriptorType = USB_DT_CS_INTERFACE;
+ alt_opts->fmt_desc.bDescriptorSubtype = UAC_FORMAT_TYPE;
+ alt_opts->fmt_desc.bFormatType = UAC_FORMAT_TYPE_I;
+ alt_opts->fmt_desc.bSubslotSize = alt_opts->ssize;
+ alt_opts->fmt_desc.bBitResolution = alt_opts->ssize * 8;
+}
+
+static int init_isoc_ep_descriptor(struct device *dev, struct usb_endpoint_descriptor *ep_desc,
+ struct f_uac2_alt_opts *alt_opts, int dir,
+ enum usb_device_speed speed, u8 addr)
+{
+ ep_desc->bLength = USB_DT_ENDPOINT_SIZE;
+ ep_desc->bDescriptorType = USB_DT_ENDPOINT;
+ ep_desc->bEndpointAddress = addr;
+ ep_desc->bmAttributes = USB_ENDPOINT_XFER_ISOC |
+ (((dir == HOST_TO_DEVICE) && !EPOUT_FBACK_IN_EN(alt_opts))
+ ? USB_ENDPOINT_SYNC_ADAPTIVE
+ : USB_ENDPOINT_SYNC_ASYNC);
+ ep_desc->bInterval = 1; /* For FS. For HS/SS, this is set later from hs_bint. */
+
+ return set_ep_max_packet_size_bint(dev, alt_opts, ep_desc, speed, (dir == DEVICE_TO_HOST));
+}
+
+static void init_isoc_ep_descriptor_comp(struct usb_ss_ep_comp_descriptor *ep_desc_comp,
+ struct usb_endpoint_descriptor *ep_desc)
+{
+ ep_desc_comp->bLength = sizeof(*ep_desc_comp),
+ ep_desc_comp->bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+ ep_desc_comp->bMaxBurst = 0,
+ ep_desc_comp->bmAttributes = 0,
+ ep_desc_comp->wBytesPerInterval = ep_desc->wMaxPacketSize;
+}
+
+static int init_alt_descriptors(struct device *dev, struct f_uac2_alt_opts *alt_opts, int ifnum,
+ u8 epaddr, int endpoints, int terminalID, int dir,
+ struct usb_string *strings)
+{
+ int status = 0;
+
+ init_as_header_desc(alt_opts, terminalID);
+ init_as_interface_desc(&alt_opts->intf_desc, ifnum, alt_opts->c.alt_num,
+ endpoints, alt_opts->name, strings);
+ init_uac_format_type_i_discrete_desc(alt_opts);
+
+ status = init_isoc_ep_descriptor(dev, &alt_opts->fs_iso_ep_desc, alt_opts,
+ dir, USB_SPEED_FULL, epaddr);
+ if (!status)
+ status = init_isoc_ep_descriptor(dev, &alt_opts->hs_iso_ep_desc, alt_opts,
+ dir, USB_SPEED_HIGH, epaddr);
+ if (!status)
+ status = init_isoc_ep_descriptor(dev, &alt_opts->ss_iso_ep_desc, alt_opts,
+ dir, USB_SPEED_SUPER, epaddr);
+
+ init_isoc_ep_descriptor_comp(&alt_opts->ss_iso_ep_desc_comp, &alt_opts->ss_iso_ep_desc);
+
+ return status;
+}
+
+static struct f_uac2_path_descriptors *build_path_descriptors(struct path_params *params,
+ struct f_uac2_alt_opts *alt_opts)
+{
+ struct f_uac2_path_descriptors *path_descs;
+ u8 srcId;
+
+ path_descs = kzalloc(sizeof(*path_descs), GFP_KERNEL);
+ if (!path_descs)
+ return NULL;
+
+ path_descs->dir = params->dir;
+ path_descs->alt_opts = alt_opts;
+
+ init_it_desc(&path_descs->it_desc, alt_opts, params);
+ srcId = path_descs->it_desc.bTerminalID;
+
+ if (((params->dir == HOST_TO_DEVICE) && FUOUT_EN(params->opts)) ||
+ ((params->dir == DEVICE_TO_HOST) && FUIN_EN(params->opts))) {
+ path_descs->fu_desc = build_fu_desc(alt_opts, params,
+ path_descs->it_desc.bTerminalID);
+ if (!path_descs->fu_desc) {
+ kfree(path_descs);
+ return NULL;
+ }
+ srcId = path_descs->fu_desc->bUnitID;
+ }
+
+ init_ot_desc(&path_descs->ot_desc, alt_opts, params, srcId);
+
+ return path_descs;
+}
+
+static void free_path_descriptors(struct f_uac2_path_descriptors *path_descs)
+{
+ kfree(path_descs->fu_desc);
+ kfree(path_descs);
+}
+
+static struct f_uac2_path_descriptors *find_path_descriptors(struct list_head *list,
+ struct f_uac2_alt_opts *alt_opts,
+ int dir)
+{
+ struct f_uac2_path_descriptors *path_descs;
+
+ list_for_each_entry(path_descs, list, list) {
+ /* Check that all options used in the path descriptors are the same */
+ if ((path_descs->dir == dir) &&
+ (!strncmp(path_descs->alt_opts->name, alt_opts->name,
+ sizeof(alt_opts->name))) &&
+ (!strncmp(path_descs->alt_opts->it_name, alt_opts->it_name,
+ sizeof(alt_opts->it_name))) &&
+ (!strncmp(path_descs->alt_opts->it_ch_name, alt_opts->it_ch_name,
+ sizeof(alt_opts->it_ch_name))) &&
+ (!strncmp(path_descs->alt_opts->ot_name, alt_opts->ot_name,
+ sizeof(alt_opts->ot_name))) &&
+ (path_descs->alt_opts->chmask == alt_opts->chmask) &&
+ (path_descs->alt_opts->terminal_type == alt_opts->terminal_type))
+ return path_descs;
+ }
+ return NULL;
+}
+
+static int add_path_descriptors(struct list_head *list, struct path_params *params,
+ struct f_uac2_alt_opts *alt_opts)
+{
+ int len = 0;
+ struct f_uac2_path_descriptors *path_descs;
+
+ if (!EP_EN(alt_opts))
+ return 0;
+
+ path_descs = find_path_descriptors(list, alt_opts, params->dir);
+
+ if (!path_descs) {
+ path_descs = build_path_descriptors(params, alt_opts);
+ if (path_descs) {
+ list_add_tail(&path_descs->list, list);
+ len += sizeof(path_descs->it_desc);
+ len += sizeof(path_descs->ot_desc);
+ if (path_descs->fu_desc)
+ len += path_descs->fu_desc->bLength;
+ }
+ }
+
+ if (path_descs) {
+ alt_opts->as_header_desc.bTerminalLink =
+ (params->dir == HOST_TO_DEVICE) ? path_descs->it_desc.bTerminalID
+ : path_descs->ot_desc.bTerminalID;
+ alt_opts->it_id = path_descs->it_desc.bTerminalID;
+ alt_opts->fu_id = path_descs->fu_desc ? path_descs->fu_desc->bUnitID : 0;
+ alt_opts->ot_id = path_descs->ot_desc.bTerminalID;
+ }
+
+ return len;
+}
+
/* Use macro to overcome line length limitation */
-#define USBDHDR(p) (struct usb_descriptor_header *)(p)
+#define USBDHDR(p) ((struct usb_descriptor_header *)(p))
-static void setup_headers(struct f_uac2_opts *opts,
- struct usb_descriptor_header **headers,
- enum usb_device_speed speed)
+static inline void add_descriptor(int i, struct usb_descriptor_header **desc_list,
+ struct usb_descriptor_header *desc)
+{
+ if (desc_list)
+ desc_list[i] = desc;
+}
+
+static int add_alt_descriptors(int i, struct usb_descriptor_header **desc_list,
+ struct f_uac2_alt_opts *alt_opts, enum usb_device_speed speed)
+{
+ add_descriptor(i++, desc_list, USBDHDR(&alt_opts->intf_desc));
+ add_descriptor(i++, desc_list, USBDHDR(&alt_opts->as_header_desc));
+ add_descriptor(i++, desc_list, USBDHDR(&alt_opts->fmt_desc));
+ if (speed == USB_SPEED_FULL)
+ add_descriptor(i++, desc_list, USBDHDR(&alt_opts->fs_iso_ep_desc));
+ else if (speed == USB_SPEED_HIGH)
+ add_descriptor(i++, desc_list, USBDHDR(&alt_opts->hs_iso_ep_desc));
+ else if (speed == USB_SPEED_SUPER || speed == USB_SPEED_SUPER_PLUS) {
+ add_descriptor(i++, desc_list, USBDHDR(&alt_opts->ss_iso_ep_desc));
+ add_descriptor(i++, desc_list, USBDHDR(&alt_opts->ss_iso_ep_desc_comp));
+ }
+
+ return i;
+}
+
+
+static int setup_headers(struct f_uac2_opts *opts,
+ struct usb_descriptor_header **headers,
+ struct list_head *path_descs,
+ enum usb_device_speed speed)
{
- struct usb_ss_ep_comp_descriptor *epout_desc_comp = NULL;
- struct usb_ss_ep_comp_descriptor *epin_desc_comp = NULL;
struct usb_ss_ep_comp_descriptor *epin_fback_desc_comp = NULL;
struct usb_ss_ep_comp_descriptor *ep_int_desc_comp = NULL;
- struct usb_endpoint_descriptor *epout_desc;
- struct usb_endpoint_descriptor *epin_desc;
struct usb_endpoint_descriptor *epin_fback_desc;
struct usb_endpoint_descriptor *ep_int_desc;
- int i;
+
+ int i = 0;
+ struct list_head *pos;
switch (speed) {
case USB_SPEED_FULL:
- epout_desc = &fs_epout_desc;
- epin_desc = &fs_epin_desc;
epin_fback_desc = &fs_epin_fback_desc;
ep_int_desc = &fs_ep_int_desc;
break;
case USB_SPEED_HIGH:
- epout_desc = &hs_epout_desc;
- epin_desc = &hs_epin_desc;
epin_fback_desc = &hs_epin_fback_desc;
ep_int_desc = &hs_ep_int_desc;
break;
default:
- epout_desc = &ss_epout_desc;
- epin_desc = &ss_epin_desc;
- epout_desc_comp = &ss_epout_desc_comp;
- epin_desc_comp = &ss_epin_desc_comp;
epin_fback_desc = &ss_epin_fback_desc;
epin_fback_desc_comp = &ss_epin_fback_desc_comp;
ep_int_desc = &ss_ep_int_desc;
ep_int_desc_comp = &ss_ep_int_desc_comp;
}
- i = 0;
- headers[i++] = USBDHDR(&iad_desc);
- headers[i++] = USBDHDR(&std_ac_if_desc);
- headers[i++] = USBDHDR(&ac_hdr_desc);
- if (EPIN_EN(opts))
- headers[i++] = USBDHDR(&in_clk_src_desc);
- if (EPOUT_EN(opts)) {
- headers[i++] = USBDHDR(&out_clk_src_desc);
- headers[i++] = USBDHDR(&usb_out_it_desc);
-
- if (FUOUT_EN(opts))
- headers[i++] = USBDHDR(out_feature_unit_desc);
+ add_descriptor(i++, headers, USBDHDR(&iad_desc));
+ add_descriptor(i++, headers, USBDHDR(&std_ac_if_desc));
+ add_descriptor(i++, headers, USBDHDR(&ac_hdr_desc));
+ if (epin_en_any(opts))
+ add_descriptor(i++, headers, USBDHDR(&in_clk_src_desc));
+ if (epout_en_any(opts))
+ add_descriptor(i++, headers, USBDHDR(&out_clk_src_desc));
+
+ list_for_each(pos, path_descs) {
+ struct f_uac2_path_descriptors *path_desc =
+ container_of(pos, struct f_uac2_path_descriptors, list);
+ add_descriptor(i++, headers, USBDHDR(&path_desc->it_desc));
+ if (path_desc->fu_desc)
+ add_descriptor(i++, headers, USBDHDR(path_desc->fu_desc));
+ add_descriptor(i++, headers, USBDHDR(&path_desc->ot_desc));
}
- if (EPIN_EN(opts)) {
- headers[i++] = USBDHDR(&io_in_it_desc);
+ // If any FU exists, add the interrupt endpoint descriptor
+ if (FUOUT_EN(opts) || FUIN_EN(opts)) {
+ add_descriptor(i++, headers, USBDHDR(ep_int_desc));
+ if (ep_int_desc_comp)
+ add_descriptor(i++, headers, USBDHDR(ep_int_desc_comp));
+ }
- if (FUIN_EN(opts))
- headers[i++] = USBDHDR(in_feature_unit_desc);
+ // If any capture interface is active
+ if (epout_en_any(opts)) {
+ struct f_uac2_alt_opts *alt_opts;
- headers[i++] = USBDHDR(&usb_in_ot_desc);
- }
+ add_descriptor(i++, headers, USBDHDR(&opts->c_alt_0_opts.intf_desc));
- if (EPOUT_EN(opts))
- headers[i++] = USBDHDR(&io_out_ot_desc);
+ if (EP_EN(&opts->c_alt_1_opts)) {
+ i = add_alt_descriptors(i, headers, &opts->c_alt_1_opts, speed);
- if (FUOUT_EN(opts) || FUIN_EN(opts)) {
- headers[i++] = USBDHDR(ep_int_desc);
- if (ep_int_desc_comp)
- headers[i++] = USBDHDR(ep_int_desc_comp);
- }
+ add_descriptor(i++, headers, USBDHDR(&as_iso_out_desc));
+ if (EPOUT_FBACK_IN_EN(&opts->c_alt_1_opts)) {
+ add_descriptor(i++, headers, USBDHDR(epin_fback_desc));
+ if (epin_fback_desc_comp)
+ add_descriptor(i++, headers, USBDHDR(epin_fback_desc_comp));
+ }
+ }
- if (EPOUT_EN(opts)) {
- headers[i++] = USBDHDR(&std_as_out_if0_desc);
- headers[i++] = USBDHDR(&std_as_out_if1_desc);
- headers[i++] = USBDHDR(&as_out_hdr_desc);
- headers[i++] = USBDHDR(&as_out_fmt1_desc);
- headers[i++] = USBDHDR(epout_desc);
- if (epout_desc_comp)
- headers[i++] = USBDHDR(epout_desc_comp);
-
- headers[i++] = USBDHDR(&as_iso_out_desc);
-
- if (EPOUT_FBACK_IN_EN(opts)) {
- headers[i++] = USBDHDR(epin_fback_desc);
- if (epin_fback_desc_comp)
- headers[i++] = USBDHDR(epin_fback_desc_comp);
+ list_for_each_entry(alt_opts, &opts->c_alt_opts, list) {
+ if (EP_EN(alt_opts)) {
+ i = add_alt_descriptors(i, headers, alt_opts, speed);
+
+ add_descriptor(i++, headers, USBDHDR(&as_iso_out_desc));
+ if (EPOUT_FBACK_IN_EN(alt_opts)) {
+ add_descriptor(i++, headers, USBDHDR(epin_fback_desc));
+ if (epin_fback_desc_comp)
+ add_descriptor(i++, headers,
+ USBDHDR(epin_fback_desc_comp));
+ }
+ }
}
}
- if (EPIN_EN(opts)) {
- headers[i++] = USBDHDR(&std_as_in_if0_desc);
- headers[i++] = USBDHDR(&std_as_in_if1_desc);
- headers[i++] = USBDHDR(&as_in_hdr_desc);
- headers[i++] = USBDHDR(&as_in_fmt1_desc);
- headers[i++] = USBDHDR(epin_desc);
- if (epin_desc_comp)
- headers[i++] = USBDHDR(epin_desc_comp);
+ // If any playback interface is active
+ if (epin_en_any(opts)) {
+ struct f_uac2_alt_opts *alt_opts;
+
+ add_descriptor(i++, headers, USBDHDR(&opts->p_alt_0_opts.intf_desc));
+
+ if (EP_EN(&opts->p_alt_1_opts)) {
+ i = add_alt_descriptors(i, headers, &opts->p_alt_1_opts, speed);
- headers[i++] = USBDHDR(&as_iso_in_desc);
+ add_descriptor(i++, headers, USBDHDR(&as_iso_in_desc));
+ }
+
+ list_for_each_entry(alt_opts, &opts->p_alt_opts, list) {
+ if (EP_EN(alt_opts)) {
+ i = add_alt_descriptors(i, headers, alt_opts, speed);
+
+ add_descriptor(i++, headers, USBDHDR(&as_iso_in_desc));
+ }
+ }
}
- headers[i] = NULL;
+
+ add_descriptor(i++, headers, NULL);
+
+ return i;
}
-static void setup_descriptor(struct f_uac2_opts *opts)
+static int setup_descriptor(struct device *dev, struct f_uac2 *uac2, struct f_uac2_opts *opts,
+ struct usb_string *strings)
{
+ int status;
+ struct usb_descriptor_header **fs_desc_list, **hs_desc_list, **ss_ssp_desc_list;
+
/* patch descriptors */
- int i = 1; /* ID's start with 1 */
-
- if (EPOUT_EN(opts))
- usb_out_it_desc.bTerminalID = i++;
- if (EPIN_EN(opts))
- io_in_it_desc.bTerminalID = i++;
- if (EPOUT_EN(opts))
- io_out_ot_desc.bTerminalID = i++;
- if (EPIN_EN(opts))
- usb_in_ot_desc.bTerminalID = i++;
- if (FUOUT_EN(opts))
- out_feature_unit_desc->bUnitID = i++;
- if (FUIN_EN(opts))
- in_feature_unit_desc->bUnitID = i++;
- if (EPOUT_EN(opts))
- out_clk_src_desc.bClockID = i++;
- if (EPIN_EN(opts))
- in_clk_src_desc.bClockID = i++;
-
- usb_out_it_desc.bCSourceID = out_clk_src_desc.bClockID;
-
- if (FUIN_EN(opts)) {
- usb_in_ot_desc.bSourceID = in_feature_unit_desc->bUnitID;
- in_feature_unit_desc->bSourceID = io_in_it_desc.bTerminalID;
- } else {
- usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID;
- }
+ int len;
+ struct list_head path_descs = LIST_HEAD_INIT(path_descs);
+ int fs_num, hs_num, ss_ssp_num;
+ struct f_uac2_alt_opts *alt_opts;
+ struct list_head *path_desc, *tmp;
+ struct path_params params;
- usb_in_ot_desc.bCSourceID = in_clk_src_desc.bClockID;
- io_in_it_desc.bCSourceID = in_clk_src_desc.bClockID;
- io_out_ot_desc.bCSourceID = out_clk_src_desc.bClockID;
+ params.id = 1; /* ID's start with 1 */
+ params.opts = opts;
+ params.strings = strings;
- if (FUOUT_EN(opts)) {
- io_out_ot_desc.bSourceID = out_feature_unit_desc->bUnitID;
- out_feature_unit_desc->bSourceID = usb_out_it_desc.bTerminalID;
- } else {
- io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID;
- }
+ len = sizeof(ac_hdr_desc);
- as_out_hdr_desc.bTerminalLink = usb_out_it_desc.bTerminalID;
- as_in_hdr_desc.bTerminalLink = usb_in_ot_desc.bTerminalID;
+ if (uac2->g_audio.out_ep) {
+ params.dir = HOST_TO_DEVICE;
+ out_clk_src_desc.bClockID = params.id++;
+ len += sizeof(out_clk_src_desc);
- iad_desc.bInterfaceCount = 1;
- ac_hdr_desc.wTotalLength = cpu_to_le16(sizeof(ac_hdr_desc));
+ init_as_interface_desc(&opts->c_alt_0_opts.intf_desc, uac2->as_out_intf, 0, 0,
+ opts->c_alt_0_opts.name, strings);
- if (EPIN_EN(opts)) {
- u16 len = le16_to_cpu(ac_hdr_desc.wTotalLength);
+ /* Audio path descriptors (input terminal -> <feature unit> -> output terminal) */
+ len += add_path_descriptors(&path_descs, ¶ms, &opts->c_alt_1_opts);
+ status = init_alt_descriptors(dev, &opts->c_alt_1_opts, uac2->as_out_intf,
+ uac2->g_audio.out_ep->address,
+ EPOUT_FBACK_IN_EN(&opts->c_alt_1_opts) ? 2 : 1,
+ opts->c_alt_1_opts.it_id, HOST_TO_DEVICE, strings);
+ if (status) {
+ dev_err(dev, "Failed to init alt descs for capture alt %d (%d)\n",
+ 1, status);
+ goto cleanup;
+ }
+
+ list_for_each_entry(alt_opts, &opts->c_alt_opts, list) {
+ len += add_path_descriptors(&path_descs, ¶ms, alt_opts);
+
+ status = init_alt_descriptors(dev, alt_opts, uac2->as_out_intf,
+ uac2->g_audio.out_ep->address,
+ EPOUT_FBACK_IN_EN(alt_opts) ? 2 : 1,
+ alt_opts->it_id, HOST_TO_DEVICE, strings);
+ if (status) {
+ dev_err(dev, "Failed to init alt descs for capture alt %d (%d)\n",
+ alt_opts->c.alt_num, status);
+ goto cleanup;
+ }
+ }
+ }
+
+ if (uac2->g_audio.in_ep) {
+ params.dir = DEVICE_TO_HOST;
+ in_clk_src_desc.bClockID = params.id++;
len += sizeof(in_clk_src_desc);
- len += sizeof(usb_in_ot_desc);
- if (FUIN_EN(opts))
- len += in_feature_unit_desc->bLength;
+ init_as_interface_desc(&opts->p_alt_0_opts.intf_desc, uac2->as_in_intf, 0, 0,
+ opts->p_alt_0_opts.name, strings);
- len += sizeof(io_in_it_desc);
- ac_hdr_desc.wTotalLength = cpu_to_le16(len);
- iad_desc.bInterfaceCount++;
+ /* Audio path descriptors (input terminal -> <feature unit> -> output terminal) */
+ len += add_path_descriptors(&path_descs, ¶ms, &opts->p_alt_1_opts);
+
+ status = init_alt_descriptors(dev, &opts->p_alt_1_opts, uac2->as_in_intf,
+ uac2->g_audio.in_ep->address, 1,
+ opts->p_alt_1_opts.ot_id, DEVICE_TO_HOST, strings);
+ if (status) {
+ dev_err(dev, "Failed to init alt descs for playback alt %d (%d)\n",
+ 1, status);
+ goto cleanup;
+ }
+
+ list_for_each_entry(alt_opts, &opts->p_alt_opts, list) {
+ len += add_path_descriptors(&path_descs, ¶ms, alt_opts);
+
+ status = init_alt_descriptors(dev, alt_opts, uac2->as_in_intf,
+ uac2->g_audio.in_ep->address, 1,
+ alt_opts->ot_id, DEVICE_TO_HOST, strings);
+ if (status) {
+ dev_err(dev, "Failed to init alt descs for playback alt %d (%d)\n",
+ alt_opts->c.alt_num, status);
+ goto cleanup;
+ }
+ }
}
- if (EPOUT_EN(opts)) {
- u16 len = le16_to_cpu(ac_hdr_desc.wTotalLength);
- len += sizeof(out_clk_src_desc);
- len += sizeof(usb_out_it_desc);
+ ac_hdr_desc.wTotalLength = cpu_to_le16(len);
- if (FUOUT_EN(opts))
- len += out_feature_unit_desc->bLength;
+ /* Count how many descriptors we have and then allocate and populate */
+ fs_num = setup_headers(opts, NULL, &path_descs, USB_SPEED_FULL);
+ hs_num = setup_headers(opts, NULL, &path_descs, USB_SPEED_HIGH);
+ ss_ssp_num = setup_headers(opts, NULL, &path_descs, USB_SPEED_SUPER);
- len += sizeof(io_out_ot_desc);
- ac_hdr_desc.wTotalLength = cpu_to_le16(len);
- iad_desc.bInterfaceCount++;
+ fs_desc_list = kzalloc((fs_num + hs_num + ss_ssp_num) * sizeof(*fs_desc_list), GFP_KERNEL);
+ if (!fs_desc_list) {
+ status = -ENOMEM;
+ goto cleanup;
}
+ hs_desc_list = fs_desc_list + fs_num;
+ ss_ssp_desc_list = hs_desc_list + hs_num;
+
+ (void) setup_headers(opts, fs_desc_list, &path_descs, USB_SPEED_FULL);
+ (void) setup_headers(opts, hs_desc_list, &path_descs, USB_SPEED_HIGH);
+ (void) setup_headers(opts, ss_ssp_desc_list, &path_descs, USB_SPEED_SUPER);
- io_in_it_desc.wTerminalType = cpu_to_le16(opts->c_terminal_type);
- io_out_ot_desc.wTerminalType = cpu_to_le16(opts->p_terminal_type);
+ /* copy descriptors, and track endpoint copies */
+ status = usb_assign_descriptors(&uac2->g_audio.func, fs_desc_list, hs_desc_list,
+ ss_ssp_desc_list, ss_ssp_desc_list);
- setup_headers(opts, fs_audio_desc, USB_SPEED_FULL);
- setup_headers(opts, hs_audio_desc, USB_SPEED_HIGH);
- setup_headers(opts, ss_audio_desc, USB_SPEED_SUPER);
+ if (status)
+ dev_err(dev, "Failed to assign descriptors (%d)\n", status);
+
+ kfree(fs_desc_list);
+
+cleanup:
+ list_for_each_safe(path_desc, tmp, &path_descs) {
+ free_path_descriptors(
+ container_of(path_desc, struct f_uac2_path_descriptors, list));
+ }
+
+ return status;
}
static int afunc_validate_opts(struct g_audio *agdev, struct device *dev)
{
struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
+ struct f_uac2_alt_opts *alt_opts;
const char *msg = NULL;
- if (!opts->p_chmask && !opts->c_chmask)
+ if (!epin_en_any(opts) && !epout_en_any(opts))
msg = "no playback and capture channels";
- else if (opts->p_chmask & ~UAC2_CHANNEL_MASK)
- msg = "unsupported playback channels mask";
- else if (opts->c_chmask & ~UAC2_CHANNEL_MASK)
- msg = "unsupported capture channels mask";
- else if ((opts->p_ssize < 1) || (opts->p_ssize > 4))
- msg = "incorrect playback sample size";
- else if ((opts->c_ssize < 1) || (opts->c_ssize > 4))
- msg = "incorrect capture sample size";
- else if (!opts->p_srates[0])
+
+ list_for_each_entry(alt_opts, &opts->p_alt_opts, list) {
+ if (alt_opts->chmask & ~UAC2_CHANNEL_MASK)
+ msg = "unsupported playback channels mask";
+ else if ((alt_opts->ssize < 1) || (alt_opts->ssize > 4))
+ msg = "incorrect playback sample size";
+ else if ((alt_opts->hs_bint < 0) || (alt_opts->hs_bint > 4))
+ msg = "incorrect playback HS/SS bInterval (1-4: fixed, 0: auto)";
+ }
+
+ list_for_each_entry(alt_opts, &opts->c_alt_opts, list) {
+ if (alt_opts->chmask & ~UAC2_CHANNEL_MASK)
+ msg = "unsupported capture channels mask";
+ else if ((alt_opts->ssize < 1) || (alt_opts->ssize > 4))
+ msg = "incorrect capture sample size";
+ else if ((alt_opts->hs_bint < 0) || (alt_opts->hs_bint > 4))
+ msg = "incorrect capture HS/SS bInterval (1-4: fixed, 0: auto)";
+ }
+
+ if (!opts->p_srates[0])
msg = "incorrect playback sampling rate";
else if (!opts->c_srates[0])
msg = "incorrect capture sampling rate";
@@ -962,11 +1004,6 @@ static int afunc_validate_opts(struct g_audio *agdev, struct device *dev)
else if ((opts->c_volume_max - opts->c_volume_min) % opts->c_volume_res)
msg = "incorrect capture volume resolution";
- else if ((opts->p_hs_bint < 0) || (opts->p_hs_bint > 4))
- msg = "incorrect playback HS/SS bInterval (1-4: fixed, 0: auto)";
- else if ((opts->c_hs_bint < 0) || (opts->c_hs_bint > 4))
- msg = "incorrect capture HS/SS bInterval (1-4: fixed, 0: auto)";
-
if (msg) {
dev_err(dev, "Error: %s\n", msg);
return -EINVAL;
@@ -1025,6 +1062,25 @@ static void init_alt_opts(struct f_uac2_alt_opts *alt_opts, struct f_uac2_opts *
alt_opts->terminal_type = (!playback) ? opts->p_terminal_type : opts->c_terminal_type;
}
+static u16 get_max_packet_size(struct f_uac2_alt_opts *alt_opts, struct list_head *list)
+{
+ u16 max_psize = max_t(u16,
+ le16_to_cpu(alt_opts->fs_iso_ep_desc.wMaxPacketSize),
+ le16_to_cpu(alt_opts->hs_iso_ep_desc.wMaxPacketSize));
+ max_psize = max_t(u16, max_psize,
+ le16_to_cpu(alt_opts->ss_iso_ep_desc.wMaxPacketSize));
+
+ list_for_each_entry(alt_opts, list, list) {
+ max_psize = max_t(u16, max_psize,
+ le16_to_cpu(alt_opts->fs_iso_ep_desc.wMaxPacketSize));
+ max_psize = max_t(u16, max_psize,
+ le16_to_cpu(alt_opts->hs_iso_ep_desc.wMaxPacketSize));
+ max_psize = max_t(u16, max_psize,
+ le16_to_cpu(alt_opts->ss_iso_ep_desc.wMaxPacketSize));
+ }
+
+ return max_psize;
+}
static int
afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
@@ -1062,272 +1118,147 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
if (IS_ERR(us))
return PTR_ERR(us);
- if (FUOUT_EN(uac2_opts)) {
- out_feature_unit_desc = build_fu_desc(uac2_opts->c_chmask);
- if (!out_feature_unit_desc)
- return -ENOMEM;
- }
- if (FUIN_EN(uac2_opts)) {
- in_feature_unit_desc = build_fu_desc(uac2_opts->p_chmask);
- if (!in_feature_unit_desc) {
- ret = -ENOMEM;
- goto err_free_fu;
- }
- }
-
iad_desc.iFunction = add_string(us, uac2_opts->function_name);
std_ac_if_desc.iInterface = add_string(us, uac2_opts->if_ctrl_name);
in_clk_src_desc.iClockSource = add_string(us, uac2_opts->clksrc_in_name);
out_clk_src_desc.iClockSource = add_string(us, uac2_opts->clksrc_out_name);
- usb_out_it_desc.iTerminal = add_string(us, uac2_opts->c_alt_1_opts.it_name);
- usb_out_it_desc.iChannelNames = add_string(us, uac2_opts->c_alt_1_opts.it_ch_name);
- io_in_it_desc.iTerminal = add_string(us, uac2_opts->p_alt_1_opts.it_name);
- io_in_it_desc.iChannelNames = add_string(us, uac2_opts->p_alt_1_opts.it_ch_name);
- usb_in_ot_desc.iTerminal = add_string(us, uac2_opts->p_alt_1_opts.ot_name);
- io_out_ot_desc.iTerminal = add_string(us, uac2_opts->c_alt_1_opts.ot_name);
- std_as_out_if0_desc.iInterface = add_string(us, uac2_opts->c_alt_0_opts.name);
- std_as_out_if1_desc.iInterface = add_string(us, uac2_opts->c_alt_1_opts.name);
- std_as_in_if0_desc.iInterface = add_string(us, uac2_opts->p_alt_0_opts.name);
- std_as_in_if1_desc.iInterface = add_string(us, uac2_opts->p_alt_0_opts.name);
-
- if (FUOUT_EN(uac2_opts)) {
- u8 *i_feature = (u8 *)out_feature_unit_desc +
- out_feature_unit_desc->bLength - 1;
- *i_feature = add_string(us, uac2_opts->c_alt_1_opts.fu_vol_name);
- }
- if (FUIN_EN(uac2_opts)) {
- u8 *i_feature = (u8 *)in_feature_unit_desc +
- in_feature_unit_desc->bLength - 1;
- *i_feature = add_string(us, uac2_opts->p_alt_1_opts.fu_vol_name);
- }
-
-
- /* Initialize the configurable parameters */
- usb_out_it_desc.bNrChannels = num_channels(uac2_opts->c_chmask);
- usb_out_it_desc.bmChannelConfig = cpu_to_le32(uac2_opts->c_chmask);
- io_in_it_desc.bNrChannels = num_channels(uac2_opts->p_chmask);
- io_in_it_desc.bmChannelConfig = cpu_to_le32(uac2_opts->p_chmask);
- as_out_hdr_desc.bNrChannels = num_channels(uac2_opts->c_chmask);
- as_out_hdr_desc.bmChannelConfig = cpu_to_le32(uac2_opts->c_chmask);
- as_in_hdr_desc.bNrChannels = num_channels(uac2_opts->p_chmask);
- as_in_hdr_desc.bmChannelConfig = cpu_to_le32(uac2_opts->p_chmask);
- as_out_fmt1_desc.bSubslotSize = uac2_opts->c_ssize;
- as_out_fmt1_desc.bBitResolution = uac2_opts->c_ssize * 8;
- as_in_fmt1_desc.bSubslotSize = uac2_opts->p_ssize;
- as_in_fmt1_desc.bBitResolution = uac2_opts->p_ssize * 8;
- if (FUOUT_EN(uac2_opts)) {
- __le32 *bma = (__le32 *)&out_feature_unit_desc->bmaControls[0];
- u32 control = 0;
-
- if (uac2_opts->c_mute_present)
- control |= CONTROL_RDWR << FU_MUTE_CTRL;
- if (uac2_opts->c_volume_present)
- control |= CONTROL_RDWR << FU_VOL_CTRL;
- *bma = cpu_to_le32(control);
- }
- if (FUIN_EN(uac2_opts)) {
- __le32 *bma = (__le32 *)&in_feature_unit_desc->bmaControls[0];
- u32 control = 0;
-
- if (uac2_opts->p_mute_present)
- control |= CONTROL_RDWR << FU_MUTE_CTRL;
- if (uac2_opts->p_volume_present)
- control |= CONTROL_RDWR << FU_VOL_CTRL;
- *bma = cpu_to_le32(control);
- }
+ /* allocate instance-specific interface IDs */
ret = usb_interface_id(cfg, fn);
if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- goto err_free_fu;
+ goto fail;
}
iad_desc.bFirstInterface = ret;
+ iad_desc.bInterfaceCount = 1;
std_ac_if_desc.bInterfaceNumber = ret;
uac2->ac_intf = ret;
uac2->ac_alt = 0;
- if (EPOUT_EN(uac2_opts)) {
+ if (epout_en_any(uac2_opts)) {
ret = usb_interface_id(cfg, fn);
if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- goto err_free_fu;
+ goto fail;
}
- std_as_out_if0_desc.bInterfaceNumber = ret;
- std_as_out_if1_desc.bInterfaceNumber = ret;
- std_as_out_if1_desc.bNumEndpoints = 1;
+
+ iad_desc.bInterfaceCount++;
+
uac2->as_out_intf = ret;
uac2->as_out_alt = 0;
-
- if (EPOUT_FBACK_IN_EN(uac2_opts)) {
- fs_epout_desc.bmAttributes =
- USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC;
- hs_epout_desc.bmAttributes =
- USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC;
- ss_epout_desc.bmAttributes =
- USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC;
- std_as_out_if1_desc.bNumEndpoints++;
- } else {
- fs_epout_desc.bmAttributes =
- USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE;
- hs_epout_desc.bmAttributes =
- USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE;
- ss_epout_desc.bmAttributes =
- USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE;
- }
}
- if (EPIN_EN(uac2_opts)) {
+ if (epin_en_any(uac2_opts)) {
ret = usb_interface_id(cfg, fn);
if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- goto err_free_fu;
+ goto fail;
}
- std_as_in_if0_desc.bInterfaceNumber = ret;
- std_as_in_if1_desc.bInterfaceNumber = ret;
+
+ iad_desc.bInterfaceCount++;
+
uac2->as_in_intf = ret;
uac2->as_in_alt = 0;
}
+ /* allocate AC interrupt endpoint */
if (FUOUT_EN(uac2_opts) || FUIN_EN(uac2_opts)) {
uac2->int_ep = usb_ep_autoconfig(gadget, &fs_ep_int_desc);
if (!uac2->int_ep) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
ret = -ENODEV;
- goto err_free_fu;
+ goto fail;
}
+ hs_ep_int_desc.bEndpointAddress = fs_ep_int_desc.bEndpointAddress;
+ ss_ep_int_desc.bEndpointAddress = fs_ep_int_desc.bEndpointAddress;
std_ac_if_desc.bNumEndpoints = 1;
}
- hs_epin_desc.bInterval = uac2_opts->p_hs_bint;
- ss_epin_desc.bInterval = uac2_opts->p_hs_bint;
- hs_epout_desc.bInterval = uac2_opts->c_hs_bint;
- ss_epout_desc.bInterval = uac2_opts->c_hs_bint;
-
- /* Calculate wMaxPacketSize according to audio bandwidth */
- ret = set_ep_max_packet_size_bint(dev, &uac2_opts->p_alt_1_opts, &fs_epin_desc,
- USB_SPEED_FULL, true);
- if (ret < 0) {
- dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- return ret;
- }
-
- ret = set_ep_max_packet_size_bint(dev, &uac2_opts->c_alt_1_opts, &fs_epout_desc,
- USB_SPEED_FULL, false);
- if (ret < 0) {
- dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- return ret;
- }
-
- ret = set_ep_max_packet_size_bint(dev, &uac2_opts->p_alt_1_opts, &hs_epin_desc,
- USB_SPEED_HIGH, true);
- if (ret < 0) {
- dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- return ret;
- }
-
- ret = set_ep_max_packet_size_bint(dev, &uac2_opts->c_alt_1_opts, &hs_epout_desc,
- USB_SPEED_HIGH, false);
- if (ret < 0) {
- dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- return ret;
- }
-
- ret = set_ep_max_packet_size_bint(dev, &uac2_opts->p_alt_1_opts, &ss_epin_desc,
- USB_SPEED_SUPER, true);
- if (ret < 0) {
- dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- return ret;
- }
-
- ret = set_ep_max_packet_size_bint(dev, &uac2_opts->c_alt_1_opts, &ss_epout_desc,
- USB_SPEED_SUPER, false);
- if (ret < 0) {
- dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- return ret;
- }
+ /* Allocate instance-specific endpoints. These use the FS version for alt mode 1.
+ * All other alt modes and speeds will be initialized to the same endpoint address
+ * during the setup_descriptor() call. The u_audio code will update the currently
+ * selected endpoint descriptor when the alt mode changes.
+ */
+ if (epout_en_any(uac2_opts)) {
+ ret = init_isoc_ep_descriptor(dev, &uac2_opts->c_alt_1_opts.fs_iso_ep_desc,
+ &uac2_opts->c_alt_1_opts, HOST_TO_DEVICE,
+ USB_SPEED_FULL, USB_DIR_OUT);
+ if (ret) {
+ dev_err(dev, "Failed to init FS isoc ep desc for capture (%d)\n", ret);
+ goto fail;
+ }
- if (EPOUT_EN(uac2_opts)) {
- agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc);
+ agdev->out_ep = usb_ep_autoconfig(gadget, &uac2_opts->c_alt_1_opts.fs_iso_ep_desc);
if (!agdev->out_ep) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
ret = -ENODEV;
- goto err_free_fu;
+ goto fail;
}
- if (EPOUT_FBACK_IN_EN(uac2_opts)) {
+ if (epout_fback_in_en_any(uac2_opts)) {
agdev->in_ep_fback = usb_ep_autoconfig(gadget,
&fs_epin_fback_desc);
if (!agdev->in_ep_fback) {
dev_err(dev, "%s:%d Error!\n",
__func__, __LINE__);
ret = -ENODEV;
- goto err_free_fu;
+ goto fail;
}
+ hs_epin_fback_desc.bEndpointAddress = fs_epin_fback_desc.bEndpointAddress;
+ ss_epin_fback_desc.bEndpointAddress = fs_epin_fback_desc.bEndpointAddress;
}
}
- if (EPIN_EN(uac2_opts)) {
- agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc);
+ if (epin_en_any(uac2_opts)) {
+ ret = init_isoc_ep_descriptor(dev, &uac2_opts->p_alt_1_opts.fs_iso_ep_desc,
+ &uac2_opts->p_alt_1_opts, HOST_TO_DEVICE,
+ USB_SPEED_FULL, USB_DIR_IN);
+ if (ret) {
+ dev_err(dev, "Failed to init FS isoc ep desc for playback (%d)\n", ret);
+ goto fail;
+ }
+
+ agdev->in_ep = usb_ep_autoconfig(gadget, &uac2_opts->p_alt_1_opts.fs_iso_ep_desc);
if (!agdev->in_ep) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
ret = -ENODEV;
- goto err_free_fu;
+ goto fail;
}
}
- agdev->in_ep_maxpsize = max_t(u16,
- le16_to_cpu(fs_epin_desc.wMaxPacketSize),
- le16_to_cpu(hs_epin_desc.wMaxPacketSize));
- agdev->out_ep_maxpsize = max_t(u16,
- le16_to_cpu(fs_epout_desc.wMaxPacketSize),
- le16_to_cpu(hs_epout_desc.wMaxPacketSize));
-
- agdev->in_ep_maxpsize = max_t(u16, agdev->in_ep_maxpsize,
- le16_to_cpu(ss_epin_desc.wMaxPacketSize));
- agdev->out_ep_maxpsize = max_t(u16, agdev->out_ep_maxpsize,
- le16_to_cpu(ss_epout_desc.wMaxPacketSize));
-
- ss_epin_desc_comp.wBytesPerInterval = ss_epin_desc.wMaxPacketSize;
- ss_epout_desc_comp.wBytesPerInterval = ss_epout_desc.wMaxPacketSize;
-
- // HS and SS endpoint addresses are copied from autoconfigured FS descriptors
- hs_ep_int_desc.bEndpointAddress = fs_ep_int_desc.bEndpointAddress;
- hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress;
- hs_epin_fback_desc.bEndpointAddress = fs_epin_fback_desc.bEndpointAddress;
- hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress;
- ss_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress;
- ss_epin_fback_desc.bEndpointAddress = fs_epin_fback_desc.bEndpointAddress;
- ss_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress;
- ss_ep_int_desc.bEndpointAddress = fs_ep_int_desc.bEndpointAddress;
-
- setup_descriptor(uac2_opts);
-
- ret = usb_assign_descriptors(fn, fs_audio_desc, hs_audio_desc, ss_audio_desc,
- ss_audio_desc);
- if (ret)
- goto err_free_fu;
+ agdev->out_ep_maxpsize = get_max_packet_size(&uac2_opts->c_alt_1_opts,
+ &uac2_opts->c_alt_opts);
+ agdev->in_ep_maxpsize = get_max_packet_size(&uac2_opts->p_alt_1_opts,
+ &uac2_opts->p_alt_opts);
+
+ setup_descriptor(dev, uac2, uac2_opts, us);
agdev->gadget = gadget;
+ // TODO: This may need some change with the audio params for the current alt mode
agdev->params.p_chmask = uac2_opts->p_chmask;
memcpy(agdev->params.p_srates, uac2_opts->p_srates,
sizeof(agdev->params.p_srates));
agdev->params.p_ssize = uac2_opts->p_ssize;
+
if (FUIN_EN(uac2_opts)) {
- agdev->params.p_fu.id = USB_IN_FU_ID;
+ agdev->params.p_fu.id = USB_IN_FU_ID(uac2_opts);
agdev->params.p_fu.mute_present = uac2_opts->p_mute_present;
agdev->params.p_fu.volume_present = uac2_opts->p_volume_present;
agdev->params.p_fu.volume_min = uac2_opts->p_volume_min;
agdev->params.p_fu.volume_max = uac2_opts->p_volume_max;
agdev->params.p_fu.volume_res = uac2_opts->p_volume_res;
}
+
+ // TODO: This may need some change with the audio params for the current alt mode
agdev->params.c_chmask = uac2_opts->c_chmask;
memcpy(agdev->params.c_srates, uac2_opts->c_srates,
sizeof(agdev->params.c_srates));
agdev->params.c_ssize = uac2_opts->c_ssize;
+
if (FUOUT_EN(uac2_opts)) {
- agdev->params.c_fu.id = USB_OUT_FU_ID;
+ agdev->params.c_fu.id = USB_OUT_FU_ID(uac2_opts);
agdev->params.c_fu.mute_present = uac2_opts->c_mute_present;
agdev->params.c_fu.volume_present = uac2_opts->c_volume_present;
agdev->params.c_fu.volume_min = uac2_opts->c_volume_min;
@@ -1338,7 +1269,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
agdev->params.fb_max = uac2_opts->fb_max;
if (FUOUT_EN(uac2_opts) || FUIN_EN(uac2_opts))
- agdev->notify = afunc_notify;
+ agdev->notify = afunc_notify;
ret = g_audio_setup(agdev, "UAC2 PCM", "UAC2_Gadget");
if (ret)
@@ -1349,11 +1280,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
err_free_descs:
usb_free_all_descriptors(fn);
agdev->gadget = NULL;
-err_free_fu:
- kfree(out_feature_unit_desc);
- out_feature_unit_desc = NULL;
- kfree(in_feature_unit_desc);
- in_feature_unit_desc = NULL;
+fail:
return ret;
}
@@ -1428,7 +1355,7 @@ afunc_notify(struct g_audio *agdev, int unit_id, int cs)
}
static int
-afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
+afunc_set_alt(struct usb_function *fn, unsigned int intf, unsigned int alt)
{
struct usb_composite_dev *cdev = fn->config->cdev;
struct f_uac2 *uac2 = func_to_uac2(fn);
@@ -1483,7 +1410,7 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
}
static int
-afunc_get_alt(struct usb_function *fn, unsigned intf)
+afunc_get_alt(struct usb_function *fn, unsigned int intf)
{
struct f_uac2 *uac2 = func_to_uac2(fn);
struct g_audio *agdev = func_to_g_audio(fn);
@@ -1561,11 +1488,11 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
"%s:%d control_selector=%d TODO!\n",
__func__, __LINE__, control_selector);
}
- } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) {
+ } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
+ (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
unsigned int is_playback = 0;
- if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID))
+ if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
is_playback = 1;
if (control_selector == UAC_FU_MUTE) {
@@ -1650,11 +1577,11 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
"%s:%d control_selector=%d TODO!\n",
__func__, __LINE__, control_selector);
}
- } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) {
+ } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
+ (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
unsigned int is_playback = 0;
- if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID))
+ if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
is_playback = 1;
if (control_selector == UAC_FU_VOLUME) {
@@ -1740,11 +1667,11 @@ out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req)
return;
}
- if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) {
+ if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
+ (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
unsigned int is_playback = 0;
- if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID))
+ if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
is_playback = 1;
if (control_selector == UAC_FU_MUTE) {
@@ -1794,8 +1721,8 @@ out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
req->complete = uac2_cs_control_sam_freq;
return w_length;
}
- } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) {
+ } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
+ (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
memcpy(&uac2->setup_cr, cr, sizeof(*cr));
req->context = agdev;
req->complete = out_rq_cur_complete;
@@ -2292,11 +2219,6 @@ static void afunc_unbind(struct usb_configuration *c, struct usb_function *f)
usb_free_all_descriptors(f);
agdev->gadget = NULL;
-
- kfree(out_feature_unit_desc);
- out_feature_unit_desc = NULL;
- kfree(in_feature_unit_desc);
- in_feature_unit_desc = NULL;
}
static struct usb_function *afunc_alloc(struct usb_function_instance *fi)
diff --git a/drivers/usb/gadget/function/u_uac2.h b/drivers/usb/gadget/function/u_uac2.h
index 8c061e588324..91171c6e493a 100644
--- a/drivers/usb/gadget/function/u_uac2.h
+++ b/drivers/usb/gadget/function/u_uac2.h
@@ -53,6 +53,9 @@ struct f_uac2_alt_0_opts {
struct f_uac2_alt_opts_common c;
char name[USB_MAX_STRING_LEN];
+
+ /* Descriptors */
+ struct usb_interface_descriptor intf_desc;
};
/* Alt modes 1+ */
@@ -75,6 +78,35 @@ struct f_uac2_alt_opts {
u8 hs_bint;
s16 terminal_type;
+ /* Descriptors */
+ struct usb_interface_descriptor intf_desc;
+ struct uac2_as_header_descriptor as_header_desc;
+ struct uac2_format_type_i_descriptor fmt_desc;
+
+ struct usb_endpoint_descriptor fs_iso_ep_desc;
+ struct usb_endpoint_descriptor hs_iso_ep_desc;
+ struct usb_endpoint_descriptor ss_iso_ep_desc;
+ struct usb_ss_ep_comp_descriptor ss_iso_ep_desc_comp;
+
+ u8 clk_id; /* Clock Source Descriptor bClockID */
+ u8 it_id; /* Input Terminal Descriptor bTerminalID */
+ u8 fu_id; /* Feature Unit Descriptor bUnitID */
+ u8 ot_id; /* Output Terminal Descriptor bTerminalID */
+};
+
+struct f_uac2_path_descriptors {
+ struct list_head list;
+
+ int dir; /* HOST_TO_DEVICE or DEVICE_TO_HOST */
+
+ /* Alt mode opts this path descriptor is from */
+ struct f_uac2_alt_opts *alt_opts;
+
+ struct uac2_input_terminal_descriptor it_desc;
+ struct uac2_output_terminal_descriptor ot_desc;
+
+ /* Feature unit is optional */
+ struct uac2_feature_unit_descriptor *fu_desc;
};
struct f_uac2_opts {
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH RFC 13/14] usb: gadget: f_uac1: support ganged volume/mute controls
2024-09-28 15:08 [PATCH RFC 00/14] usb: gadget: f_uac: Add support for alt mode settings crwulff
` (11 preceding siblings ...)
2024-09-28 15:09 ` [PATCH RFC 12/14] usb: gadget: f_uac2: " crwulff
@ 2024-09-28 15:09 ` crwulff
2024-09-28 15:09 ` [PATCH RFC 14/14] usb: gadget: f_uac2: " crwulff
13 siblings, 0 replies; 15+ messages in thread
From: crwulff @ 2024-09-28 15:09 UTC (permalink / raw)
To: linux-usb
Cc: Pavel Hofman, Greg Kroah-Hartman, James Gruber, Jeff Johnson,
John Keeping, Jonathan Corbet, Lee Jones, Perr Zhang, linux-doc,
linux-kernel, Chris Wulff
From: Chris Wulff <crwulff@gmail.com>
When multiple feature units exist due to differences
in other terminal descriptors, they still represent
the same volume/mute controls.
Signed-off-by: Chris Wulff <crwulff@gmail.com>
---
drivers/usb/gadget/function/f_uac1.c | 186 ++++++++++++++++++++-------
1 file changed, 136 insertions(+), 50 deletions(-)
diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c
index 7803957e4f82..c29cbe4cea14 100644
--- a/drivers/usb/gadget/function/f_uac1.c
+++ b/drivers/usb/gadget/function/f_uac1.c
@@ -27,8 +27,7 @@
/* UAC1 spec: 3.7.2.3 Audio Channel Cluster Format */
#define UAC1_CHANNEL_MASK 0x0FFF
-#define USB_OUT_FU_ID(_opts) (_opts->c_alt_1_opts.fu_id)
-#define USB_IN_FU_ID(_opts) (_opts->p_alt_1_opts.fu_id)
+#define USB_FU_ID(_alt_opts) ((_alt_opts) ? (_alt_opts)->fu_id : 0)
#define EP_EN(_alt_opts) ((_alt_opts) && ((_alt_opts)->chmask != 0))
#define FUIN_EN(_opts) ((_opts)->p_mute_present \
@@ -83,6 +82,25 @@ static inline struct f_uac1_opts *g_audio_to_uac1_opts(struct g_audio *audio)
return container_of(audio->func.fi, struct f_uac1_opts, func_inst);
}
+static inline struct f_uac1_alt_opts *get_alt_opts(struct f_uac1_opts *opts, int alt, int dir)
+{
+ struct f_uac1_alt_opts *alt_opts;
+
+ if (alt == 0)
+ return NULL;
+
+ if (alt == 1)
+ return (dir == HOST_TO_DEVICE) ? &opts->c_alt_1_opts : &opts->p_alt_1_opts;
+
+ list_for_each_entry(alt_opts, (dir == HOST_TO_DEVICE) ? &opts->c_alt_opts
+ : &opts->p_alt_opts, list) {
+ if (alt_opts->c.alt_num == alt)
+ return alt_opts;
+ }
+
+ return NULL;
+}
+
/*
* DESCRIPTORS ... most are static, but strings and full
* configuration descriptors are built on demand.
@@ -244,16 +262,53 @@ static void audio_notify_complete(struct usb_ep *_ep, struct usb_request *req)
usb_ep_free_request(_ep, req);
}
-static int audio_notify(struct g_audio *audio, int unit_id, int cs)
+static int audio_notify_one(struct g_audio *audio, int unit_id, int cs);
+
+static int audio_notify_multiple(struct g_audio *audio, int unit_id, int cs, int source_id)
{
struct f_uac1 *uac1 = func_to_uac1(&audio->func);
- struct usb_request *req;
- struct uac1_status_word *msg;
+ struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio);
int ret;
+ struct f_uac1_alt_opts *alt_opts;
+ struct list_head *alt_opts_list;
if (!uac1->int_ep->enabled)
return 0;
+ if (unit_id != source_id) {
+ ret = audio_notify_one(audio, unit_id, cs);
+ if (ret)
+ return ret;
+ }
+
+ alt_opts_list = (unit_id == USB_FU_ID(&opts->c_alt_1_opts)) ? &opts->c_alt_opts
+ : &opts->p_alt_opts;
+
+ /* Notify all other ganged controls */
+ list_for_each_entry(alt_opts, alt_opts_list, list) {
+ if ((USB_FU_ID(alt_opts) > unit_id) && (USB_FU_ID(alt_opts) != source_id)) {
+ unit_id = USB_FU_ID(alt_opts);
+ ret = audio_notify_one(audio, unit_id, cs);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int audio_notify(struct g_audio *audio, int unit_id, int cs)
+{
+ return audio_notify_multiple(audio, unit_id, cs, 0);
+}
+
+static int audio_notify_one(struct g_audio *audio, int unit_id, int cs)
+{
+ struct f_uac1 *uac1 = func_to_uac1(&audio->func);
+ struct usb_request *req;
+ struct uac1_status_word *msg;
+ int ret;
+
if (atomic_inc_return(&uac1->int_count) > UAC1_DEF_INT_REQ_NUM) {
atomic_dec(&uac1->int_count);
return 0;
@@ -297,6 +352,40 @@ static int audio_notify(struct g_audio *audio, int unit_id, int cs)
return ret;
}
+static struct f_uac1_alt_opts *
+find_feature_unit(struct f_uac1_opts *opts, u8 entity_id, int *is_playback)
+{
+ struct f_uac1_alt_opts *alt_opts;
+
+ if (FUOUT_EN(opts)) {
+ if (is_playback)
+ *is_playback = 0;
+
+ if (entity_id == USB_FU_ID(&opts->c_alt_1_opts))
+ return &opts->c_alt_1_opts;
+
+ list_for_each_entry(alt_opts, &opts->c_alt_opts, list) {
+ if (entity_id == USB_FU_ID(alt_opts))
+ return alt_opts;
+ }
+ }
+
+ if (FUIN_EN(opts)) {
+ if (is_playback)
+ *is_playback = 1;
+
+ if (entity_id == USB_FU_ID(&opts->p_alt_1_opts))
+ return &opts->p_alt_1_opts;
+
+ list_for_each_entry(alt_opts, &opts->p_alt_opts, list) {
+ if (entity_id == USB_FU_ID(alt_opts))
+ return alt_opts;
+ }
+ }
+
+ return NULL;
+}
+
static int
in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
{
@@ -309,14 +398,10 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
u8 entity_id = (w_index >> 8) & 0xff;
u8 control_selector = w_value >> 8;
int value = -EOPNOTSUPP;
+ unsigned int is_playback = 0;
+ struct f_uac1_alt_opts *alt_opts = find_feature_unit(opts, entity_id, &is_playback);
- if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
- unsigned int is_playback = 0;
-
- if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
- is_playback = 1;
-
+ if (alt_opts) {
if (control_selector == UAC_FU_MUTE) {
unsigned int mute;
@@ -360,14 +445,10 @@ in_rq_min(struct usb_function *fn, const struct usb_ctrlrequest *cr)
u8 entity_id = (w_index >> 8) & 0xff;
u8 control_selector = w_value >> 8;
int value = -EOPNOTSUPP;
+ unsigned int is_playback = 0;
+ struct f_uac1_alt_opts *alt_opts = find_feature_unit(opts, entity_id, &is_playback);
- if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
- unsigned int is_playback = 0;
-
- if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
- is_playback = 1;
-
+ if (alt_opts) {
if (control_selector == UAC_FU_VOLUME) {
__le16 r;
s16 min_db;
@@ -407,14 +488,10 @@ in_rq_max(struct usb_function *fn, const struct usb_ctrlrequest *cr)
u8 entity_id = (w_index >> 8) & 0xff;
u8 control_selector = w_value >> 8;
int value = -EOPNOTSUPP;
+ unsigned int is_playback = 0;
+ struct f_uac1_alt_opts *alt_opts = find_feature_unit(opts, entity_id, &is_playback);
- if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
- unsigned int is_playback = 0;
-
- if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
- is_playback = 1;
-
+ if (alt_opts) {
if (control_selector == UAC_FU_VOLUME) {
__le16 r;
s16 max_db;
@@ -454,14 +531,10 @@ in_rq_res(struct usb_function *fn, const struct usb_ctrlrequest *cr)
u8 entity_id = (w_index >> 8) & 0xff;
u8 control_selector = w_value >> 8;
int value = -EOPNOTSUPP;
+ unsigned int is_playback = 0;
+ struct f_uac1_alt_opts *alt_opts = find_feature_unit(opts, entity_id, &is_playback);
- if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
- unsigned int is_playback = 0;
-
- if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
- is_playback = 1;
-
+ if (alt_opts) {
if (control_selector == UAC_FU_VOLUME) {
__le16 r;
s16 res_db;
@@ -501,24 +574,25 @@ out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req)
u16 w_value = le16_to_cpu(cr->wValue);
u8 entity_id = (w_index >> 8) & 0xff;
u8 control_selector = w_value >> 8;
+ unsigned int is_playback = 0;
+ struct f_uac1_alt_opts *alt_opts = find_feature_unit(opts, entity_id, &is_playback);
if (req->status != 0) {
dev_dbg(&cdev->gadget->dev, "completion err %d\n", req->status);
return;
}
- if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
- unsigned int is_playback = 0;
-
- if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
- is_playback = 1;
-
+ if (alt_opts) {
if (control_selector == UAC_FU_MUTE) {
u8 mute = *(u8 *)req->buf;
u_audio_set_mute(audio, is_playback, mute);
+ /* We also need to send notify for ganged controls */
+ audio_notify_multiple(audio, USB_FU_ID(is_playback ? &opts->p_alt_1_opts
+ : &opts->c_alt_1_opts),
+ control_selector, entity_id);
+
return;
} else if (control_selector == UAC_FU_VOLUME) {
__le16 *c = req->buf;
@@ -527,6 +601,11 @@ out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req)
volume = le16_to_cpu(*c);
u_audio_set_volume(audio, is_playback, volume);
+ /* We also need to send notify for ganged controls */
+ audio_notify_multiple(audio, USB_FU_ID(is_playback ? &opts->p_alt_1_opts
+ : &opts->c_alt_1_opts),
+ control_selector, entity_id);
+
return;
} else {
dev_err(&audio->gadget->dev,
@@ -556,8 +635,7 @@ out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
u8 entity_id = (w_index >> 8) & 0xff;
u8 control_selector = w_value >> 8;
- if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
+ if (find_feature_unit(opts, entity_id, NULL)) {
memcpy(&uac1->setup_cr, cr, sizeof(*cr));
req->context = audio;
req->complete = out_rq_cur_complete;
@@ -751,14 +829,10 @@ static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
struct device *dev = &gadget->dev;
struct g_audio *audio = func_to_g_audio(f);
struct f_uac1 *uac1 = func_to_uac1(f);
+ struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio);
+ struct f_uac1_alt_opts *alt_opts = NULL;
int ret = 0;
- /* No i/f has more than 2 alt settings */
- if (alt > 1) {
- dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- return -EINVAL;
- }
-
if (intf == uac1->ac_intf) {
/* Control I/f has only 1 AltSetting - 0 */
if (alt) {
@@ -777,6 +851,12 @@ static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
}
if (intf == uac1->as_out_intf) {
+ alt_opts = get_alt_opts(opts, alt, HOST_TO_DEVICE);
+ if (alt && !alt_opts) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ return -EINVAL;
+ }
+
uac1->as_out_alt = alt;
if (alt)
@@ -784,6 +864,12 @@ static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
else
u_audio_stop_capture(&uac1->g_audio);
} else if (intf == uac1->as_in_intf) {
+ alt_opts = get_alt_opts(opts, alt, DEVICE_TO_HOST);
+ if (alt && !alt_opts) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ return -EINVAL;
+ }
+
uac1->as_in_alt = alt;
if (alt)
@@ -1841,7 +1927,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
audio->params.c_ssize = audio_opts->c_ssize;
if (FUIN_EN(audio_opts)) {
- audio->params.p_fu.id = USB_IN_FU_ID(audio_opts);
+ audio->params.p_fu.id = USB_FU_ID(&audio_opts->p_alt_1_opts);
audio->params.p_fu.mute_present = audio_opts->p_mute_present;
audio->params.p_fu.volume_present =
audio_opts->p_volume_present;
@@ -1857,7 +1943,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
audio->params.p_ssize = audio_opts->p_ssize;
if (FUOUT_EN(audio_opts)) {
- audio->params.c_fu.id = USB_OUT_FU_ID(audio_opts);
+ audio->params.c_fu.id = USB_FU_ID(&audio_opts->c_alt_1_opts);
audio->params.c_fu.mute_present = audio_opts->c_mute_present;
audio->params.c_fu.volume_present =
audio_opts->c_volume_present;
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH RFC 14/14] usb: gadget: f_uac2: support ganged volume/mute controls
2024-09-28 15:08 [PATCH RFC 00/14] usb: gadget: f_uac: Add support for alt mode settings crwulff
` (12 preceding siblings ...)
2024-09-28 15:09 ` [PATCH RFC 13/14] usb: gadget: f_uac1: support ganged volume/mute controls crwulff
@ 2024-09-28 15:09 ` crwulff
13 siblings, 0 replies; 15+ messages in thread
From: crwulff @ 2024-09-28 15:09 UTC (permalink / raw)
To: linux-usb
Cc: Pavel Hofman, Greg Kroah-Hartman, James Gruber, Jeff Johnson,
John Keeping, Jonathan Corbet, Lee Jones, Perr Zhang, linux-doc,
linux-kernel, Chris Wulff
From: Chris Wulff <crwulff@gmail.com>
When multiple feature units exist due to differences
in other terminal descriptors, they still represent
the same volume/mute controls.
Signed-off-by: Chris Wulff <crwulff@gmail.com>
---
drivers/usb/gadget/function/f_uac2.c | 171 +++++++++++++++++++++------
1 file changed, 133 insertions(+), 38 deletions(-)
diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index c30fbd062793..427a95b043db 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -35,8 +35,8 @@
*/
#define USB_OUT_CLK_ID (out_clk_src_desc.bClockID)
#define USB_IN_CLK_ID (in_clk_src_desc.bClockID)
-#define USB_OUT_FU_ID(_opts) (_opts->c_alt_1_opts.fu_id)
-#define USB_IN_FU_ID(_opts) (_opts->p_alt_1_opts.fu_id)
+#define USB_FU_ID(_alt_opts) ((_alt_opts) ? (_alt_opts)->fu_id : 0)
+
#define CONTROL_ABSENT 0
#define CONTROL_RDONLY 1
@@ -54,7 +54,6 @@
#define UNFLW_CTRL 8
#define OVFLW_CTRL 10
-
#define EP_EN(_alt_opts) ((_alt_opts) && ((_alt_opts)->chmask != 0))
#define FUIN_EN(_opts) (EP_EN(&_opts->p_alt_1_opts) \
&& ((_opts)->p_mute_present \
@@ -112,6 +111,59 @@ struct f_uac2_opts *g_audio_to_uac2_opts(struct g_audio *agdev)
static int afunc_notify(struct g_audio *agdev, int unit_id, int cs);
+static inline struct f_uac2_alt_opts *get_alt_opts(struct f_uac2_opts *opts, int alt, int dir)
+{
+ struct f_uac2_alt_opts *alt_opts;
+
+ if (alt == 0)
+ return NULL;
+
+ if (alt == 1)
+ return (dir == HOST_TO_DEVICE) ? &opts->c_alt_1_opts : &opts->p_alt_1_opts;
+
+ list_for_each_entry(alt_opts, (dir == HOST_TO_DEVICE) ? &opts->c_alt_opts
+ : &opts->p_alt_opts, list) {
+ if (alt_opts->c.alt_num == alt)
+ return alt_opts;
+ }
+
+ return NULL;
+}
+
+static struct f_uac2_alt_opts *
+find_feature_unit(struct f_uac2_opts *opts, u8 entity_id, int *is_playback)
+{
+ struct f_uac2_alt_opts *alt_opts;
+
+ if (FUOUT_EN(opts)) {
+ if (is_playback)
+ *is_playback = 0;
+
+ if (entity_id == USB_FU_ID(&opts->c_alt_1_opts))
+ return &opts->c_alt_1_opts;
+
+ list_for_each_entry(alt_opts, &opts->c_alt_opts, list) {
+ if (entity_id == USB_FU_ID(alt_opts))
+ return alt_opts;
+ }
+ }
+
+ if (FUIN_EN(opts)) {
+ if (is_playback)
+ *is_playback = 1;
+
+ if (entity_id == USB_FU_ID(&opts->p_alt_1_opts))
+ return &opts->p_alt_1_opts;
+
+ list_for_each_entry(alt_opts, &opts->p_alt_opts, list) {
+ if (entity_id == USB_FU_ID(alt_opts))
+ return alt_opts;
+ }
+ }
+
+ return NULL;
+}
+
/* --------- USB Function Interface ------------- */
static struct usb_interface_assoc_descriptor iad_desc = {
@@ -1243,7 +1295,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
agdev->params.p_ssize = uac2_opts->p_ssize;
if (FUIN_EN(uac2_opts)) {
- agdev->params.p_fu.id = USB_IN_FU_ID(uac2_opts);
+ agdev->params.p_fu.id = USB_FU_ID(&uac2_opts->p_alt_1_opts);
agdev->params.p_fu.mute_present = uac2_opts->p_mute_present;
agdev->params.p_fu.volume_present = uac2_opts->p_volume_present;
agdev->params.p_fu.volume_min = uac2_opts->p_volume_min;
@@ -1258,7 +1310,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
agdev->params.c_ssize = uac2_opts->c_ssize;
if (FUOUT_EN(uac2_opts)) {
- agdev->params.c_fu.id = USB_OUT_FU_ID(uac2_opts);
+ agdev->params.c_fu.id = USB_FU_ID(&uac2_opts->c_alt_1_opts);
agdev->params.c_fu.mute_present = uac2_opts->c_mute_present;
agdev->params.c_fu.volume_present = uac2_opts->c_volume_present;
agdev->params.c_fu.volume_min = uac2_opts->c_volume_min;
@@ -1295,8 +1347,49 @@ afunc_notify_complete(struct usb_ep *_ep, struct usb_request *req)
usb_ep_free_request(_ep, req);
}
+static int afunc_notify_one(struct g_audio *agdev, int unit_id, int cs);
+
+static int afunc_notify_multiple(struct g_audio *agdev, int unit_id, int cs, int source_id)
+{
+ struct f_uac2 *uac2 = func_to_uac2(&agdev->func);
+ struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
+ int ret;
+ struct f_uac2_alt_opts *alt_opts;
+ struct list_head *alt_opts_list;
+
+ if (!uac2->int_ep->enabled)
+ return 0;
+
+ if (unit_id != source_id) {
+ ret = afunc_notify_one(agdev, unit_id, cs);
+ if (ret)
+ return ret;
+ }
+
+ alt_opts_list = (unit_id == USB_FU_ID(&opts->c_alt_1_opts)) ? &opts->c_alt_opts
+ : &opts->p_alt_opts;
+
+ /* Notify all other ganged controls */
+ list_for_each_entry(alt_opts, alt_opts_list, list) {
+ if ((USB_FU_ID(alt_opts) > unit_id) && (USB_FU_ID(alt_opts) != source_id)) {
+ unit_id = USB_FU_ID(alt_opts);
+ ret = afunc_notify_one(agdev, unit_id, cs);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
static int
afunc_notify(struct g_audio *agdev, int unit_id, int cs)
+{
+ return afunc_notify_multiple(agdev, unit_id, cs, 0);
+}
+
+static int
+afunc_notify_one(struct g_audio *agdev, int unit_id, int cs)
{
struct f_uac2 *uac2 = func_to_uac2(&agdev->func);
struct usb_request *req;
@@ -1304,9 +1397,6 @@ afunc_notify(struct g_audio *agdev, int unit_id, int cs)
u16 w_index, w_value;
int ret;
- if (!uac2->int_ep->enabled)
- return 0;
-
if (atomic_inc_return(&uac2->int_count) > UAC2_DEF_INT_REQ_NUM) {
atomic_dec(&uac2->int_count);
return 0;
@@ -1360,16 +1450,12 @@ afunc_set_alt(struct usb_function *fn, unsigned int intf, unsigned int alt)
struct usb_composite_dev *cdev = fn->config->cdev;
struct f_uac2 *uac2 = func_to_uac2(fn);
struct g_audio *agdev = func_to_g_audio(fn);
+ struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
+ struct f_uac2_alt_opts *alt_opts = NULL;
struct usb_gadget *gadget = cdev->gadget;
struct device *dev = &gadget->dev;
int ret = 0;
- /* No i/f has more than 2 alt settings */
- if (alt > 1) {
- dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
- return -EINVAL;
- }
-
if (intf == uac2->ac_intf) {
/* Control I/f has only 1 AltSetting - 0 */
if (alt) {
@@ -1388,6 +1474,12 @@ afunc_set_alt(struct usb_function *fn, unsigned int intf, unsigned int alt)
}
if (intf == uac2->as_out_intf) {
+ alt_opts = get_alt_opts(opts, alt, HOST_TO_DEVICE);
+ if (alt && !alt_opts) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ return -EINVAL;
+ }
+
uac2->as_out_alt = alt;
if (alt)
@@ -1395,6 +1487,12 @@ afunc_set_alt(struct usb_function *fn, unsigned int intf, unsigned int alt)
else
u_audio_stop_capture(&uac2->g_audio);
} else if (intf == uac2->as_in_intf) {
+ alt_opts = get_alt_opts(opts, alt, DEVICE_TO_HOST);
+ if (alt && !alt_opts) {
+ dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
+ return -EINVAL;
+ }
+
uac2->as_in_alt = alt;
if (alt)
@@ -1463,6 +1561,8 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
u8 control_selector = w_value >> 8;
int value = -EOPNOTSUPP;
u32 p_srate, c_srate;
+ unsigned int is_playback = 0;
+ struct f_uac2_alt_opts *alt_opts = find_feature_unit(opts, entity_id, &is_playback);
u_audio_get_playback_srate(agdev, &p_srate);
u_audio_get_capture_srate(agdev, &c_srate);
@@ -1488,13 +1588,7 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
"%s:%d control_selector=%d TODO!\n",
__func__, __LINE__, control_selector);
}
- } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
- unsigned int is_playback = 0;
-
- if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
- is_playback = 1;
-
+ } else if (alt_opts) {
if (control_selector == UAC_FU_MUTE) {
unsigned int mute;
@@ -1539,6 +1633,8 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
u8 entity_id = (w_index >> 8) & 0xff;
u8 control_selector = w_value >> 8;
int value = -EOPNOTSUPP;
+ unsigned int is_playback = 0;
+ struct f_uac2_alt_opts *alt_opts = find_feature_unit(opts, entity_id, &is_playback);
if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) {
if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
@@ -1577,13 +1673,7 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
"%s:%d control_selector=%d TODO!\n",
__func__, __LINE__, control_selector);
}
- } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
- unsigned int is_playback = 0;
-
- if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
- is_playback = 1;
-
+ } else if (alt_opts) {
if (control_selector == UAC_FU_VOLUME) {
struct cntrl_range_lay2 r;
s16 max_db, min_db, res_db;
@@ -1654,31 +1744,32 @@ out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req)
{
struct g_audio *agdev = req->context;
struct usb_composite_dev *cdev = agdev->func.config->cdev;
- struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
struct f_uac2 *uac2 = func_to_uac2(&agdev->func);
+ struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
struct usb_ctrlrequest *cr = &uac2->setup_cr;
u16 w_index = le16_to_cpu(cr->wIndex);
u16 w_value = le16_to_cpu(cr->wValue);
u8 entity_id = (w_index >> 8) & 0xff;
u8 control_selector = w_value >> 8;
+ unsigned int is_playback;
+ struct f_uac2_alt_opts *alt_opts = find_feature_unit(opts, entity_id, &is_playback);
if (req->status != 0) {
dev_dbg(&cdev->gadget->dev, "completion err %d\n", req->status);
return;
}
- if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
- unsigned int is_playback = 0;
-
- if (FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts)))
- is_playback = 1;
-
+ if (alt_opts) {
if (control_selector == UAC_FU_MUTE) {
u8 mute = *(u8 *)req->buf;
u_audio_set_mute(agdev, is_playback, mute);
+ /* We also need to send notify for ganged controls */
+ afunc_notify_multiple(agdev, USB_FU_ID(is_playback ? &opts->p_alt_1_opts
+ : &opts->c_alt_1_opts),
+ control_selector, entity_id);
+
return;
} else if (control_selector == UAC_FU_VOLUME) {
struct cntrl_cur_lay2 *c = req->buf;
@@ -1687,6 +1778,11 @@ out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req)
volume = le16_to_cpu(c->wCUR);
u_audio_set_volume(agdev, is_playback, volume);
+ /* We also need to send notify for ganged controls */
+ afunc_notify_multiple(agdev, USB_FU_ID(is_playback ? &opts->p_alt_1_opts
+ : &opts->c_alt_1_opts),
+ control_selector, entity_id);
+
return;
} else {
dev_err(&agdev->gadget->dev,
@@ -1721,8 +1817,7 @@ out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
req->complete = uac2_cs_control_sam_freq;
return w_length;
}
- } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID(opts))) ||
- (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID(opts)))) {
+ } else if (find_feature_unit(opts, entity_id, NULL)) {
memcpy(&uac2->setup_cr, cr, sizeof(*cr));
req->context = agdev;
req->complete = out_rq_cur_complete;
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
end of thread, other threads:[~2024-09-28 15:09 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-09-28 15:08 [PATCH RFC 00/14] usb: gadget: f_uac: Add support for alt mode settings crwulff
2024-09-28 15:08 ` [PATCH RFC 01/14] usb: gadget: f_uac: Refactor common configfs attribute defines used in UAC1/2 crwulff
2024-09-28 15:08 ` [PATCH RFC 02/14] usb: gadget: f_uac1: Fix fs/hs/ss descriptors to have correct values crwulff
2024-09-28 15:08 ` [PATCH RFC 03/14] usb: gadget: f_uac1: Add adaptive sync support for capture crwulff
2024-09-28 15:08 ` [PATCH RFC 04/14] usb: gadget: f_uac2: Move max packet size code to a common header crwulff
2024-09-28 15:08 ` [PATCH RFC 05/14] usb: gadget: f_uac1: Add hs_bint to configfs crwulff
2024-09-28 15:08 ` [PATCH RFC 06/14] usb: gadget: f_uac1: Add terminal type attributes crwulff
2024-09-28 15:08 ` [PATCH RFC 07/14] usb: gadget: f_uac1: Add alt mode settings interface crwulff
2024-09-28 15:08 ` [PATCH RFC 08/14] usb: gadget: f_uac2: " crwulff
2024-09-28 15:09 ` [PATCH RFC 09/14] usb: gadget: f_uac1: Make string table dynamic with strings from all alt modes crwulff
2024-09-28 15:09 ` [PATCH RFC 10/14] usb: gadget: f_uac2: " crwulff
2024-09-28 15:09 ` [PATCH RFC 11/14] usb: gadget: f_uac1: Generate dynamic descriptors based on alt opts crwulff
2024-09-28 15:09 ` [PATCH RFC 12/14] usb: gadget: f_uac2: " crwulff
2024-09-28 15:09 ` [PATCH RFC 13/14] usb: gadget: f_uac1: support ganged volume/mute controls crwulff
2024-09-28 15:09 ` [PATCH RFC 14/14] usb: gadget: f_uac2: " crwulff
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox