Linux USB
 help / color / mirror / Atom feed
* [PATCH v3 0/3] usb: gadget: f_uac2: allow explicit channel count independent of channel mask
@ 2026-11-03 21:14 Kyle Farrell
  2026-11-03 21:14 ` [PATCH v3 1/3] usb: gadget: f_uac2: avoid truncating calculated bandwidth Kyle Farrell
                   ` (2 more replies)
  0 siblings, 3 replies; 7+ messages in thread
From: Kyle Farrell @ 2026-11-03 21:14 UTC (permalink / raw)
  To: nkapron, gregkh
  Cc: corbet, skhan, rdunlap, linux-usb, linux-doc, flavra,
	Kyle Farrell

USB Audio Class 2.0 permits up to 255 channels in an audio channel cluster
via bNrChannels. Independently, a channel-location bitmap is specified via
bmChannelConfig for up to 27 predefined spatial locations. Setting
bmChannelConfig to zero indicates that the channels have no predefined
spatial positions.

The current UAC2 gadget driver derives bNrChannels from the number of set
bits in bmChannelConfig. Since bmChannelConfig provides 27 predefined
spatial locations this limits playback and capture streams to 27 channels.

This series allows channel counts up to the UAC2 limit of 255 without
requiring predefined spatial channel locations in bmChannelConfig.
Existing ConfigFS channel-mask configuration continues to work unchanged.
New optional playback and capture count attributes allow the number of
channels to be specified separately from the channel-location bitmap.

The series first corrects bandwidth calculation truncation, then
updates the shared u_audio implementation to accept resolved channel
counts, and finally adds explicit channel-count configuration to UAC2:

Patch 1:
  Widen the bandwidth calculation to 32 bits to prevent truncation
  before the calculated packet size is checked against the endpoint
  maximum.

Patch 2:
  Pass resolved playback and capture channel counts explicitly into
  the UAC1/UAC2 shared u_audio implementation instead of deriving them
  from channel masks.

Patch 3:
  Add ConfigFS playback/capture channel counts to the UAC2 function
  driver and use them when generating descriptors and stream
  configuration, allowing counts beyond the 27 predefined channel
  locations.

Runtime dependency:
  A separately submitted fix for u_audio_iso_complete() prevents an
  additional residue frame from exceeding the endpoint's maxpacket
  size. With allowed channel counts increasing by ~10x this existing
  issue becomes more readily exposed. The fix is not included in this
  series and was submitted previously.
  Link: https://lore.kernel.org/linux-usb/20261026231026.721536-1-kaf@nwlink.com/


Testing
Testing was performed using a Raspberry Pi Zero 2 W running the
rpi-6.18.y kernel with this patch series applied. Streaming and
descriptor validation were performed against both Linux and macOS
hosts. Windows hosts were not tested.

UAC1
  - Verified playback/capture operation via alsaloop.
  - No descriptor or behavioral changes observed.

UAC2
  - Verified legacy channel-mask configuration (up to 27 channels).
  - Verified explicit channel-count mode with 2, 28, 61, 62, 96 channels.
  - Verified invalid channel-count/channel-mask combinations are rejected.
  - Verified Feature Unit descriptor size limits at 61/62 channel boundary.
  - Verified descriptors including:
      * bNrChannels
      * bmChannelConfig
      * Feature Unit presence and bLength
      * AudioControl wTotalLength
      * endpoint wMaxPacketSize
  - Verified ALSA-reported playback and capture channel counts.
  - Verified streaming using alsaloop.

Changes in v3:
  - Widen bandwidth calculation to 32-bit.
  - Remove now-unused channel mask fields from uac_params.
  - Document dependency on the separately submitted residue frame
    packet size fix (u_audio_iso_complete()).
  - Remove Assisted-by: LLM attribution
  - Link to v2: https://lore.kernel.org/linux-usb/20261016162534.384844-1-kaf@nwlink.com/

Changes in v2:
  - Edit commit messages for clarity and brevity.
  - Add Assisted-by: LLM attribution.
  - No code changes.
  - Link to v1: https://lore.kernel.org/linux-usb/20260729054115.889174-1-kaf@nwlink.com/

Kyle Farrell (3):
  usb: gadget: f_uac2: avoid truncating calculated bandwidth
  usb: gadget: u_audio: pass channel counts explicitly
  usb: gadget: f_uac2: allow explicit channel count independent of
    channel mask

 .../ABI/testing/configfs-usb-gadget-uac2      | 18 +++-
 Documentation/usb/gadget-testing.rst          | 18 +++-
 drivers/usb/gadget/function/f_uac1.c          |  4 +-
 drivers/usb/gadget/function/f_uac2.c          | 84 +++++++++++++------
 drivers/usb/gadget/function/u_audio.c         | 32 ++++---
 drivers/usb/gadget/function/u_audio.h         |  4 +-
 drivers/usb/gadget/function/u_uac2.h          |  4 +
 7 files changed, 113 insertions(+), 51 deletions(-)


base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
prerequisite-patch-id: 252c6902eb4bd4f6456cd86e4c123e8001e70a11
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v3 1/3] usb: gadget: f_uac2: avoid truncating calculated bandwidth
  2026-11-03 21:14 [PATCH v3 0/3] usb: gadget: f_uac2: allow explicit channel count independent of channel mask Kyle Farrell
@ 2026-11-03 21:14 ` Kyle Farrell
  2026-11-03 21:25   ` sashiko-bot
  2026-11-03 21:14 ` [PATCH v3 2/3] usb: gadget: u_audio: pass channel counts explicitly Kyle Farrell
  2026-11-03 21:14 ` [PATCH v3 3/3] usb: gadget: f_uac2: allow explicit channel count independent of channel mask Kyle Farrell
  2 siblings, 1 reply; 7+ messages in thread
From: Kyle Farrell @ 2026-11-03 21:14 UTC (permalink / raw)
  To: nkapron, gregkh
  Cc: corbet, skhan, rdunlap, linux-usb, linux-doc, flavra,
	Kyle Farrell

get_max_bw_for_bint() calculates the required bandwidth using 32-bit
arithmetic and stores the result in a u16. A result above U16_MAX
is truncated before set_ep_max_packet_size_bint() compares it against
the endpoint maximum. The truncated value can appear to fit, bypassing
the clamp and warning and advertising a too-small wMaxPacketSize.

Use u32 for the calculated value, the return type of
get_max_bw_for_bint() and the caller's local variable, and print it
with %u. A packet size above the endpoint maximum remains clamped
before it is stored in the 16-bit wMaxPacketSize field.

Signed-off-by: Kyle Farrell <kaf@nwlink.com>
---
 drivers/usb/gadget/function/f_uac2.c | 22 ++++++++++++----------
 1 file changed, 12 insertions(+), 10 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index d8cf710085a0..b519382df0b3 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -668,18 +668,19 @@ static int get_max_srate(const int *srates)
 	return max_srate;
 }
 
-static int get_max_bw_for_bint(const struct f_uac2_opts *uac2_opts,
+static u32 get_max_bw_for_bint(const struct f_uac2_opts *uac2_opts,
 	u8 bint, unsigned int factor, bool is_playback)
 {
-	int chmask, srate, ssize;
-	u16 max_size_bw;
+	unsigned int channels;
+	int srate, ssize;
+	u32 max_size_bw;
 
 	if (is_playback) {
-		chmask = uac2_opts->p_chmask;
+		channels = num_channels(uac2_opts->p_chmask);
 		srate = get_max_srate(uac2_opts->p_srates);
 		ssize = uac2_opts->p_ssize;
 	} else {
-		chmask = uac2_opts->c_chmask;
+		channels = num_channels(uac2_opts->c_chmask);
 		srate = get_max_srate(uac2_opts->c_srates);
 		ssize = uac2_opts->c_ssize;
 	}
@@ -689,11 +690,11 @@ static int get_max_bw_for_bint(const struct f_uac2_opts *uac2_opts,
 		// 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 *
+		max_size_bw = channels * ssize *
 			(DIV_ROUND_UP(srate, factor / (1 << (bint - 1))));
 	} else {
 		// adding 1 frame provision for Win10
-		max_size_bw = num_channels(chmask) * ssize *
+		max_size_bw = channels * ssize *
 			(DIV_ROUND_UP(srate, factor / (1 << (bint - 1))) + 1);
 	}
 	return max_size_bw;
@@ -703,7 +704,8 @@ static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac2_o
 	struct usb_endpoint_descriptor *ep_desc,
 	enum usb_device_speed speed, bool is_playback)
 {
-	u16 max_size_bw, max_size_ep;
+	u32 max_size_bw;
+	u16 max_size_ep;
 	u8 bint, opts_bint;
 	char *dir;
 
@@ -749,11 +751,11 @@ static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac2_o
 
 	if (max_size_bw <= max_size_ep)
 		dev_dbg(dev,
-			"%s %s: Would use wMaxPacketSize %d and bInterval %d\n",
+			"%s %s: Would use wMaxPacketSize %u 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",
+			"%s %s: Req. wMaxPacketSize %u 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;
 	}
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v3 2/3] usb: gadget: u_audio: pass channel counts explicitly
  2026-11-03 21:14 [PATCH v3 0/3] usb: gadget: f_uac2: allow explicit channel count independent of channel mask Kyle Farrell
  2026-11-03 21:14 ` [PATCH v3 1/3] usb: gadget: f_uac2: avoid truncating calculated bandwidth Kyle Farrell
@ 2026-11-03 21:14 ` Kyle Farrell
  2026-11-03 21:24   ` sashiko-bot
  2026-11-03 21:14 ` [PATCH v3 3/3] usb: gadget: f_uac2: allow explicit channel count independent of channel mask Kyle Farrell
  2 siblings, 1 reply; 7+ messages in thread
From: Kyle Farrell @ 2026-11-03 21:14 UTC (permalink / raw)
  To: nkapron, gregkh
  Cc: corbet, skhan, rdunlap, linux-usb, linux-doc, flavra,
	Kyle Farrell

u_audio currently derives the channel count from the channel mask in
struct uac_params in several places. Instead, pass the resolved count
explicitly, so the shared audio implementation does not depend on how
an individual USB audio function represents its channel configuration.

Initialize the channel counts from the existing masks in f_uac1 and
f_uac2. Remove the now-unused channel mask fields from uac_params.
Preserve existing behavior.

Signed-off-by: Kyle Farrell <kaf@nwlink.com>
---
 drivers/usb/gadget/function/f_uac1.c  |  4 ++--
 drivers/usb/gadget/function/f_uac2.c  |  4 ++--
 drivers/usb/gadget/function/u_audio.c | 32 ++++++++++++---------------
 drivers/usb/gadget/function/u_audio.h |  4 ++--
 4 files changed, 20 insertions(+), 24 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c
index 7a81cd176abd..7d912696cec2 100644
--- a/drivers/usb/gadget/function/f_uac1.c
+++ b/drivers/usb/gadget/function/f_uac1.c
@@ -1447,7 +1447,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
 
 	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->params.c_chmask = audio_opts->c_chmask;
+	audio->params.c_channels = num_channels(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;
@@ -1460,7 +1460,7 @@ 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;
 	}
-	audio->params.p_chmask = audio_opts->p_chmask;
+	audio->params.p_channels = num_channels(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;
diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index b519382df0b3..19578fbc6af5 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -1309,7 +1309,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
 
 	agdev->gadget = gadget;
 
-	agdev->params.p_chmask = uac2_opts->p_chmask;
+	agdev->params.p_channels = num_channels(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;
@@ -1321,7 +1321,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
 		agdev->params.p_fu.volume_max = uac2_opts->p_volume_max;
 		agdev->params.p_fu.volume_res = uac2_opts->p_volume_res;
 	}
-	agdev->params.c_chmask = uac2_opts->c_chmask;
+	agdev->params.c_channels = num_channels(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;
diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c
index f0ee83abcebc..de0622ee72ab 100644
--- a/drivers/usb/gadget/function/u_audio.c
+++ b/drivers/usb/gadget/function/u_audio.c
@@ -390,25 +390,22 @@ static int uac_pcm_open(struct snd_pcm_substream *substream)
 	struct uac_params *params;
 	struct uac_rtd_params *prm;
 	int p_ssize, c_ssize;
-	int p_chmask, c_chmask;
 
 	audio_dev = uac->audio_dev;
 	params = &audio_dev->params;
 	p_ssize = params->p_ssize;
 	c_ssize = params->c_ssize;
-	p_chmask = params->p_chmask;
-	c_chmask = params->c_chmask;
 	uac->p_residue_mil = 0;
 
 	runtime->hw = uac_pcm_hardware;
 
 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 		runtime->hw.formats = uac_ssize_to_fmt(p_ssize);
-		runtime->hw.channels_min = num_channels(p_chmask);
+		runtime->hw.channels_min = params->p_channels;
 		prm = &uac->p_prm;
 	} else {
 		runtime->hw.formats = uac_ssize_to_fmt(c_ssize);
-		runtime->hw.channels_min = num_channels(c_chmask);
+		runtime->hw.channels_min = params->c_channels;
 		prm = &uac->c_prm;
 	}
 
@@ -736,8 +733,7 @@ int u_audio_start_playback(struct g_audio *audio_dev)
 		factor = 8000;
 
 	/* pre-compute some values for iso_complete() */
-	uac->p_framesize = params->p_ssize *
-			    num_channels(params->p_chmask);
+	uac->p_framesize = params->p_ssize * params->p_channels;
 	uac->p_interval = factor / (1 << (ep_desc->bInterval - 1));
 	p_pktsize = min_t(unsigned int,
 				uac->p_framesize *
@@ -1205,7 +1201,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
 	struct snd_pcm *pcm;
 	struct snd_kcontrol *kctl;
 	struct uac_params *params;
-	int p_chmask, c_chmask;
+	unsigned int p_channels, c_channels;
 	int i, err;
 
 	if (!g_audio)
@@ -1218,10 +1214,10 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
 	uac->audio_dev = g_audio;
 
 	params = &g_audio->params;
-	p_chmask = params->p_chmask;
-	c_chmask = params->c_chmask;
+	p_channels = params->p_channels;
+	c_channels = params->c_channels;
 
-	if (c_chmask) {
+	if (c_channels) {
 		struct uac_rtd_params *prm = &uac->c_prm;
 
 		spin_lock_init(&prm->lock);
@@ -1245,7 +1241,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
 		}
 	}
 
-	if (p_chmask) {
+	if (p_channels) {
 		struct uac_rtd_params *prm = &uac->p_prm;
 
 		spin_lock_init(&prm->lock);
@@ -1284,7 +1280,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
 	 * Create a substream only for non-zero channel streams
 	 */
 	err = snd_pcm_new(uac->card, pcm_name, 0,
-			       p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm);
+			       p_channels ? 1 : 0, c_channels ? 1 : 0, &pcm);
 	if (err < 0)
 		goto snd_fail;
 
@@ -1299,12 +1295,12 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
 	 * Create mixer and controls
 	 * Create only if it's required on USB side
 	 */
-	if ((c_chmask && g_audio->in_ep_fback)
-			|| (p_chmask && params->p_fu.id)
-			|| (c_chmask && params->c_fu.id))
+	if ((c_channels && g_audio->in_ep_fback)
+			|| (p_channels && params->p_fu.id)
+			|| (c_channels && params->c_fu.id))
 		strscpy(card->mixername, card_name);
 
-	if (c_chmask && g_audio->in_ep_fback) {
+	if (c_channels && g_audio->in_ep_fback) {
 		kctl = snd_ctl_new1(&u_audio_controls[UAC_FBACK_CTRL],
 				    &uac->c_prm);
 		if (!kctl) {
@@ -1320,7 +1316,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
 			goto snd_fail;
 	}
 
-	if (p_chmask) {
+	if (p_channels) {
 		kctl = snd_ctl_new1(&u_audio_controls[UAC_P_PITCH_CTRL],
 				    &uac->p_prm);
 		if (!kctl) {
diff --git a/drivers/usb/gadget/function/u_audio.h b/drivers/usb/gadget/function/u_audio.h
index 9512b8fccfaa..2e39b49ddacb 100644
--- a/drivers/usb/gadget/function/u_audio.h
+++ b/drivers/usb/gadget/function/u_audio.h
@@ -40,13 +40,13 @@ struct uac_fu_params {
 
 struct uac_params {
 	/* playback */
-	int p_chmask;	/* channel mask */
+	unsigned int p_channels;	/* resolved number of channels */
 	int p_srates[UAC_MAX_RATES];	/* available rates in Hz (0 terminated list) */
 	int p_ssize;	/* sample size */
 	struct uac_fu_params p_fu;	/* Feature Unit parameters */
 
 	/* capture */
-	int c_chmask;	/* channel mask */
+	unsigned int c_channels;	/* resolved number of channels */
 	int c_srates[UAC_MAX_RATES];	/* available rates in Hz (0 terminated list) */
 	int c_ssize;	/* sample size */
 	struct uac_fu_params c_fu;	/* Feature Unit parameters */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v3 3/3] usb: gadget: f_uac2: allow explicit channel count independent of channel mask
  2026-11-03 21:14 [PATCH v3 0/3] usb: gadget: f_uac2: allow explicit channel count independent of channel mask Kyle Farrell
  2026-11-03 21:14 ` [PATCH v3 1/3] usb: gadget: f_uac2: avoid truncating calculated bandwidth Kyle Farrell
  2026-11-03 21:14 ` [PATCH v3 2/3] usb: gadget: u_audio: pass channel counts explicitly Kyle Farrell
@ 2026-11-03 21:14 ` Kyle Farrell
  2026-11-03 21:24   ` sashiko-bot
  2 siblings, 1 reply; 7+ messages in thread
From: Kyle Farrell @ 2026-11-03 21:14 UTC (permalink / raw)
  To: nkapron, gregkh
  Cc: corbet, skhan, rdunlap, linux-usb, linux-doc, flavra,
	Kyle Farrell

The UAC2 gadget driver currently derives bNrChannels from the number
of set bits in bmChannelConfig. Since the bmChannelConfig
channel-location bitmap provides only 27 predefined channel locations,
this limits playback and capture streams to 27 channels. UAC2 allows
bNrChannels to specify up to 255 channels, including channels without
predefined locations in bmChannelConfig.

Add ConfigFS p_channels and c_channels attributes to configure the
playback and capture channel counts explicitly.  Preserve the existing
p_chmask and c_chmask interface and semantics. The default value of
zero for p_channels and c_channels continues to derive the channel count
from the corresponding p_chmask and c_chmask.

When an explicit channel count and a nonzero channel mask are both
specified, require the number of set bits in the mask to match the
channel count. Partial channel-location assignments permitted by UAC2
are therefore not supported. Additionally, reject Feature Unit
configurations whose descriptor length would exceed the one-byte
bLength field.

Signed-off-by: Kyle Farrell <kaf@nwlink.com>
---
 .../ABI/testing/configfs-usb-gadget-uac2      | 18 ++++-
 Documentation/usb/gadget-testing.rst          | 18 ++++-
 drivers/usb/gadget/function/f_uac2.c          | 66 ++++++++++++++-----
 drivers/usb/gadget/function/u_uac2.h          |  4 ++
 4 files changed, 85 insertions(+), 21 deletions(-)

diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac2 b/Documentation/ABI/testing/configfs-usb-gadget-uac2
index 133e995c3e92..8e264843e492 100644
--- a/Documentation/ABI/testing/configfs-usb-gadget-uac2
+++ b/Documentation/ABI/testing/configfs-usb-gadget-uac2
@@ -5,7 +5,14 @@ Description:
 		The attributes:
 
 		=====================	=======================================
-		c_chmask		capture channel mask
+		c_chmask		capture channel-location mask. The number of
+					capture channels is derived from the number of
+					set bits unless c_channels is specified.
+		c_channels		explicit capture channel count. A value of 0
+					derives the channel count from c_chmask. When
+					nonzero, c_chmask specifies channel locations
+					and may be 0. If both c_channels and c_chmask
+					are nonzero, the values must agree.
 		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)
@@ -20,7 +27,14 @@ Description:
 		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_chmask		playback channel-location mask. The number of
+					playback channels is derived from the number of
+					set bits unless p_channels is specified.
+		p_channels		explicit playback channel count. A value of 0
+					derives the channel count from p_chmask. When
+					nonzero, p_chmask specifies channel locations
+					and may be 0. If both p_channels and p_chmask
+					are nonzero, the values must agree.
 		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)
diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst
index 4921e5307d49..c050218291d1 100644
--- a/Documentation/usb/gadget-testing.rst
+++ b/Documentation/usb/gadget-testing.rst
@@ -741,7 +741,14 @@ The function name to use when creating the function directory is "uac2".
 The uac2 function provides these attributes in its function directory:
 
 	================ ====================================================
-	c_chmask         capture channel mask
+	c_chmask         capture channel-location mask. The number of
+			 capture channels is derived from the number of
+			 set bits unless c_channels is specified.
+	c_channels	 explicit capture channel count. A value of 0
+			 derives the channel count from c_chmask. When
+			 nonzero, c_chmask specifies channel locations
+			 and may be 0. If both c_channels and c_chmask
+			 are nonzero, the values must agree.
 	c_srate          list of capture sampling rates (comma-separated)
 	c_ssize          capture sample size (bytes)
 	c_sync           capture synchronization type (async/adaptive)
@@ -752,7 +759,14 @@ The uac2 function provides these attributes in its function directory:
 	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_chmask         playback channel-location mask. The number of
+			 playback channels is derived from the number of
+			 set bits unless p_channels is specified.
+	p_channels	 explicit playback channel count. A value of 0
+			 derives the channel count from p_chmask. When
+			 nonzero, p_chmask specifies channel locations
+			 and may be 0. If both p_channels and p_chmask
+			 are nonzero, the values must agree.
 	p_srate          list of playback sampling rates (comma-separated)
 	p_ssize          playback sample size (bytes)
 	p_mute_present   playback mute control enable
diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index 19578fbc6af5..5973484a4233 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -50,8 +50,17 @@
 #define UNFLW_CTRL	8
 #define OVFLW_CTRL	10
 
-#define EPIN_EN(_opts) ((_opts)->p_chmask != 0)
-#define EPOUT_EN(_opts) ((_opts)->c_chmask != 0)
+static unsigned int uac2_num_channels(bool is_playback,
+				      const struct f_uac2_opts *opts)
+{
+	u8 channels = is_playback ? opts->p_channels : opts->c_channels;
+	u32 chmask = is_playback ? opts->p_chmask : opts->c_chmask;
+
+	return channels ? channels : num_channels(chmask);
+}
+
+#define EPIN_EN(_opts) (uac2_num_channels(true, (_opts)) != 0)
+#define EPOUT_EN(_opts) (uac2_num_channels(false, (_opts)) != 0)
 #define FUIN_EN(_opts) (EPIN_EN(_opts) \
 				&& ((_opts)->p_mute_present \
 				|| (_opts)->p_volume_present))
@@ -676,11 +685,11 @@ static u32 get_max_bw_for_bint(const struct f_uac2_opts *uac2_opts,
 	u32 max_size_bw;
 
 	if (is_playback) {
-		channels = num_channels(uac2_opts->p_chmask);
+		channels = uac2_num_channels(true, uac2_opts);
 		srate = get_max_srate(uac2_opts->p_srates);
 		ssize = uac2_opts->p_ssize;
 	} else {
-		channels = num_channels(uac2_opts->c_chmask);
+		channels = uac2_num_channels(false, uac2_opts);
 		srate = get_max_srate(uac2_opts->c_srates);
 		ssize = uac2_opts->c_ssize;
 	}
@@ -766,10 +775,9 @@ static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac2_o
 	return 0;
 }
 
-static struct uac2_feature_unit_descriptor *build_fu_desc(int chmask)
+static struct uac2_feature_unit_descriptor *build_fu_desc(unsigned int channels)
 {
 	struct uac2_feature_unit_descriptor *fu_desc;
-	int channels = num_channels(chmask);
 	int fu_desc_size = UAC2_DT_FEATURE_UNIT_SIZE(channels);
 
 	fu_desc = kzalloc(fu_desc_size, GFP_KERNEL);
@@ -978,13 +986,27 @@ static int afunc_validate_opts(struct g_audio *agdev, struct device *dev)
 {
 	struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
 	const char *msg = NULL;
+	unsigned int p_channels = uac2_num_channels(true, opts);
+	unsigned int c_channels = uac2_num_channels(false, opts);
 
-	if (!opts->p_chmask && !opts->c_chmask)
-		msg = "no playback and capture channels";
-	else if (opts->p_chmask & ~UAC2_CHANNEL_MASK)
+	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 (!p_channels && !c_channels)
+		msg = "no playback and capture channels";
+	else if (opts->p_channels && opts->p_chmask &&
+		 opts->p_channels != num_channels(opts->p_chmask))
+		msg = "playback channel count does not match channel mask";
+	else if (opts->c_channels && opts->c_chmask &&
+		 opts->c_channels != num_channels(opts->c_chmask))
+		msg = "capture channel count does not match channel mask";
+	else if (FUIN_EN(opts) &&
+		 UAC2_DT_FEATURE_UNIT_SIZE(p_channels) > U8_MAX)
+		msg = "too many playback channels for feature unit descriptor";
+	else if (FUOUT_EN(opts) &&
+		 UAC2_DT_FEATURE_UNIT_SIZE(c_channels) > U8_MAX)
+		msg = "too many capture channels for feature unit descriptor";
 	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))
@@ -1061,12 +1083,16 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
 		return PTR_ERR(us);
 
 	if (FUOUT_EN(uac2_opts)) {
-		out_feature_unit_desc = build_fu_desc(uac2_opts->c_chmask);
+		unsigned int c_channels = uac2_num_channels(false, uac2_opts);
+
+		out_feature_unit_desc = build_fu_desc(c_channels);
 		if (!out_feature_unit_desc)
 			return -ENOMEM;
 	}
 	if (FUIN_EN(uac2_opts)) {
-		in_feature_unit_desc = build_fu_desc(uac2_opts->p_chmask);
+		unsigned int p_channels = uac2_num_channels(true, uac2_opts);
+
+		in_feature_unit_desc = build_fu_desc(p_channels);
 		if (!in_feature_unit_desc) {
 			ret = -ENOMEM;
 			goto err_free_fu;
@@ -1101,13 +1127,13 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
 
 
 	/* Initialize the configurable parameters */
-	usb_out_it_desc.bNrChannels = num_channels(uac2_opts->c_chmask);
+	usb_out_it_desc.bNrChannels = uac2_num_channels(false, uac2_opts);
 	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.bNrChannels = uac2_num_channels(true, uac2_opts);
 	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.bNrChannels = uac2_num_channels(false, uac2_opts);
 	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.bNrChannels = uac2_num_channels(true, uac2_opts);
 	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;
@@ -1309,7 +1335,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
 
 	agdev->gadget = gadget;
 
-	agdev->params.p_channels = num_channels(uac2_opts->p_chmask);
+	agdev->params.p_channels = uac2_num_channels(true, uac2_opts);
 	memcpy(agdev->params.p_srates, uac2_opts->p_srates,
 			sizeof(agdev->params.p_srates));
 	agdev->params.p_ssize = uac2_opts->p_ssize;
@@ -1321,7 +1347,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
 		agdev->params.p_fu.volume_max = uac2_opts->p_volume_max;
 		agdev->params.p_fu.volume_res = uac2_opts->p_volume_res;
 	}
-	agdev->params.c_channels = num_channels(uac2_opts->c_chmask);
+	agdev->params.c_channels = uac2_num_channels(false, uac2_opts);
 	memcpy(agdev->params.c_srates, uac2_opts->c_srates,
 			sizeof(agdev->params.c_srates));
 	agdev->params.c_ssize = uac2_opts->c_ssize;
@@ -2091,10 +2117,12 @@ end:									\
 CONFIGFS_ATTR(f_uac2_opts_, name)
 
 UAC2_ATTRIBUTE(u32, p_chmask);
+UAC2_ATTRIBUTE(u8, p_channels);
 UAC2_RATE_ATTRIBUTE(p_srate);
 UAC2_ATTRIBUTE(u32, p_ssize);
 UAC2_ATTRIBUTE(u8, p_hs_bint);
 UAC2_ATTRIBUTE(u32, c_chmask);
+UAC2_ATTRIBUTE(u8, c_channels);
 UAC2_RATE_ATTRIBUTE(c_srate);
 UAC2_ATTRIBUTE_SYNC(c_sync);
 UAC2_ATTRIBUTE(u32, c_ssize);
@@ -2134,10 +2162,12 @@ UAC2_ATTRIBUTE(s16, c_terminal_type);
 
 static struct configfs_attribute *f_uac2_attrs[] = {
 	&f_uac2_opts_attr_p_chmask,
+	&f_uac2_opts_attr_p_channels,
 	&f_uac2_opts_attr_p_srate,
 	&f_uac2_opts_attr_p_ssize,
 	&f_uac2_opts_attr_p_hs_bint,
 	&f_uac2_opts_attr_c_chmask,
+	&f_uac2_opts_attr_c_channels,
 	&f_uac2_opts_attr_c_srate,
 	&f_uac2_opts_attr_c_ssize,
 	&f_uac2_opts_attr_c_hs_bint,
@@ -2207,10 +2237,12 @@ static struct usb_function_instance *afunc_alloc_inst(void)
 				    &f_uac2_func_type);
 
 	opts->p_chmask = UAC2_DEF_PCHMASK;
+	opts->p_channels = UAC2_DEF_PCHANNELS;
 	opts->p_srates[0] = UAC2_DEF_PSRATE;
 	opts->p_ssize = UAC2_DEF_PSSIZE;
 	opts->p_hs_bint = UAC2_DEF_PHSBINT;
 	opts->c_chmask = UAC2_DEF_CCHMASK;
+	opts->c_channels = UAC2_DEF_CCHANNELS;
 	opts->c_srates[0] = UAC2_DEF_CSRATE;
 	opts->c_ssize = UAC2_DEF_CSSIZE;
 	opts->c_hs_bint = UAC2_DEF_CHSBINT;
diff --git a/drivers/usb/gadget/function/u_uac2.h b/drivers/usb/gadget/function/u_uac2.h
index 0df808289ded..bacd841c9df6 100644
--- a/drivers/usb/gadget/function/u_uac2.h
+++ b/drivers/usb/gadget/function/u_uac2.h
@@ -17,10 +17,12 @@
 #include "uac_common.h"
 
 #define UAC2_DEF_PCHMASK 0x3
+#define UAC2_DEF_PCHANNELS 0  /* 0 = derive from p_chmask */
 #define UAC2_DEF_PSRATE 48000
 #define UAC2_DEF_PSSIZE 2
 #define UAC2_DEF_PHSBINT 0
 #define UAC2_DEF_CCHMASK 0x3
+#define UAC2_DEF_CCHANNELS 0  /* 0 = derive from c_chmask */
 #define UAC2_DEF_CSRATE 64000
 #define UAC2_DEF_CSSIZE 2
 #define UAC2_DEF_CHSBINT 0
@@ -46,11 +48,13 @@ struct f_uac2_opts {
 	int				p_srates[UAC_MAX_RATES];
 	int				p_ssize;
 	u8				p_hs_bint;
+	u8				p_channels;
 	int				c_chmask;
 	int				c_srates[UAC_MAX_RATES];
 	int				c_ssize;
 	int				c_sync;
 	u8				c_hs_bint;
+	u8				c_channels;
 
 	bool			p_mute_present;
 	bool			p_volume_present;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH v3 3/3] usb: gadget: f_uac2: allow explicit channel count independent of channel mask
  2026-11-03 21:14 ` [PATCH v3 3/3] usb: gadget: f_uac2: allow explicit channel count independent of channel mask Kyle Farrell
@ 2026-11-03 21:24   ` sashiko-bot
  0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-11-03 21:24 UTC (permalink / raw)
  To: Kyle Farrell; +Cc: linux-usb

> The UAC2 gadget driver currently derives bNrChannels from the number
> of set bits in bmChannelConfig. Since the bmChannelConfig
> channel-location bitmap provides only 27 predefined channel locations,
> this limits playback and capture streams to 27 channels. UAC2 allows
> bNrChannels to specify up to 255 channels, including channels without
> predefined locations in bmChannelConfig.
> 
> Add ConfigFS p_channels and c_channels attributes to configure the
> playback and capture channel counts explicitly.  Preserve the existing
> p_chmask and c_chmask interface and semantics. The default value of
> zero for p_channels and c_channels continues to derive the channel count
> from the corresponding p_chmask and c_chmask.
> 
> When an explicit channel count and a nonzero channel mask are both
> specified, require the number of set bits in the mask to match the
> channel count. Partial channel-location assignments permitted by UAC2
> are therefore not supported. Additionally, reject Feature Unit
> configurations whose descriptor length would exceed the one-byte
> bLength field.
> 
> Signed-off-by: Kyle Farrell <kaf@nwlink.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261103211434.996135-1-kaf@nwlink.com?part=3


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v3 2/3] usb: gadget: u_audio: pass channel counts explicitly
  2026-11-03 21:14 ` [PATCH v3 2/3] usb: gadget: u_audio: pass channel counts explicitly Kyle Farrell
@ 2026-11-03 21:24   ` sashiko-bot
  0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-11-03 21:24 UTC (permalink / raw)
  To: Kyle Farrell; +Cc: linux-usb

> u_audio currently derives the channel count from the channel mask in
> struct uac_params in several places. Instead, pass the resolved count
> explicitly, so the shared audio implementation does not depend on how
> an individual USB audio function represents its channel configuration.
> 
> Initialize the channel counts from the existing masks in f_uac1 and
> f_uac2. Remove the now-unused channel mask fields from uac_params.
> Preserve existing behavior.
> 
> Signed-off-by: Kyle Farrell <kaf@nwlink.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261103211434.996135-1-kaf@nwlink.com?part=2


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v3 1/3] usb: gadget: f_uac2: avoid truncating calculated bandwidth
  2026-11-03 21:14 ` [PATCH v3 1/3] usb: gadget: f_uac2: avoid truncating calculated bandwidth Kyle Farrell
@ 2026-11-03 21:25   ` sashiko-bot
  0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-11-03 21:25 UTC (permalink / raw)
  To: Kyle Farrell; +Cc: linux-usb

> get_max_bw_for_bint() calculates the required bandwidth using 32-bit
> arithmetic and stores the result in a u16. A result above U16_MAX
> is truncated before set_ep_max_packet_size_bint() compares it against
> the endpoint maximum. The truncated value can appear to fit, bypassing
> the clamp and warning and advertising a too-small wMaxPacketSize.
> 
> Use u32 for the calculated value, the return type of
> get_max_bw_for_bint() and the caller's local variable, and print it
> with %u. A packet size above the endpoint maximum remains clamped
> before it is stored in the 16-bit wMaxPacketSize field.
> 
> Signed-off-by: Kyle Farrell <kaf@nwlink.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261103211434.996135-1-kaf@nwlink.com?part=1


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-11-03 21:25 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-03 21:14 [PATCH v3 0/3] usb: gadget: f_uac2: allow explicit channel count independent of channel mask Kyle Farrell
2026-11-03 21:14 ` [PATCH v3 1/3] usb: gadget: f_uac2: avoid truncating calculated bandwidth Kyle Farrell
2026-11-03 21:25   ` sashiko-bot
2026-11-03 21:14 ` [PATCH v3 2/3] usb: gadget: u_audio: pass channel counts explicitly Kyle Farrell
2026-11-03 21:24   ` sashiko-bot
2026-11-03 21:14 ` [PATCH v3 3/3] usb: gadget: f_uac2: allow explicit channel count independent of channel mask Kyle Farrell
2026-11-03 21:24   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox