lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20170630100036.10547-4-julian@jusst.de>
Date:   Fri, 30 Jun 2017 12:00:36 +0200
From:   Julian Scheel <julian@...st.de>
To:     Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
        Jonathan Corbet <corbet@....net>,
        Felipe Balbi <balbi@...nel.org>,
        Julian Scheel <julian@...st.de>,
        Ruslan Bilovol <ruslan.bilovol@...il.com>,
        Peter Chen <peter.chen@....com>, Sekhar Nori <nsekhar@...com>,
        Thierry Reding <thierry.reding@...il.com>,
        linux-kernel@...r.kernel.org, linux-usb@...r.kernel.org,
        linux-doc@...r.kernel.org
Subject: [PATCHv2 3/3] usb: gadget: f_uac*: Support multiple sampling rates

Implement support for multiple sampling rates in the USB Audio gadgets.
A list of sampling rates can be specified via configfs. All enabled
sampling rates are sent to the USB host on request. When the host
selects a sampling rate the internal active rate is updated. The
currently configured rates are exposed through amixer controls. Also on
pcm open from userspace the requested rated is checked against the
currently configured rate of the host.

Signed-off-by: Julian Scheel <julian@...st.de>
---
 Documentation/ABI/testing/configfs-usb-gadget-uac1 |   4 +-
 Documentation/usb/gadget-testing.txt               |   8 +-
 drivers/usb/gadget/function/f_uac1.c               | 120 +++++++++++++----
 drivers/usb/gadget/function/f_uac2.c               | 146 +++++++++++++++------
 drivers/usb/gadget/function/u_audio.c              | 119 ++++++++++++++++-
 drivers/usb/gadget/function/u_audio.h              |   9 +-
 drivers/usb/gadget/function/u_uac.h                |  68 +++++++++-
 7 files changed, 396 insertions(+), 78 deletions(-)

diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac1 b/Documentation/ABI/testing/configfs-usb-gadget-uac1
index abfe447c848f..ad2fa4a00918 100644
--- a/Documentation/ABI/testing/configfs-usb-gadget-uac1
+++ b/Documentation/ABI/testing/configfs-usb-gadget-uac1
@@ -5,10 +5,10 @@ Description:
 		The attributes:
 
 		c_chmask - capture channel mask
-		c_srate - capture sampling rate
+		c_srate - list of capture sampling rates (comma-separataed)
 		c_ssize - capture sample size (bytes)
 		p_chmask - playback channel mask
-		p_srate - playback sampling rate
+		p_srate - list of playback sampling rates (comma-separated)
 		p_ssize - playback sample size (bytes)
 		req_number - the number of pre-allocated request
 			for both capture and playback
diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt
index fbc397d17e98..855555878880 100644
--- a/Documentation/usb/gadget-testing.txt
+++ b/Documentation/usb/gadget-testing.txt
@@ -629,10 +629,10 @@ 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_srate - capture sampling rate
+	c_srate - list of capture sampling rates (comma-separated)
 	c_ssize - capture sample size (bytes)
 	p_chmask - playback channel mask
-	p_srate - playback sampling rate
+	p_srate - list of playback sampling rates (comma-separated)
 	p_ssize - playback sample size (bytes)
 	req_number - the number of pre-allocated request for both capture
 		     and playback
@@ -790,10 +790,10 @@ The function name to use when creating the function directory is "uac1".
 The uac1 function provides these attributes in its function directory:
 
 	c_chmask - capture channel mask
-	c_srate - capture sampling rate
+	c_srate - list of capture sampling rates (comma-separated)
 	c_ssize - capture sample size (bytes)
 	p_chmask - playback channel mask
-	p_srate - playback sampling rate
+	p_srate - list of playback sampling rates (comma-separated)
 	p_ssize - playback sample size (bytes)
 	req_number - the number of pre-allocated request for both capture
 		     and playback
diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c
index 6e86e4f704e1..d14777973b9f 100644
--- a/drivers/usb/gadget/function/f_uac1.c
+++ b/drivers/usb/gadget/function/f_uac1.c
@@ -2,6 +2,7 @@
  * f_uac1.c -- USB Audio Class 1.0 Function (using u_audio API)
  *
  * Copyright (C) 2016 Ruslan Bilovol <ruslan.bilovol@...il.com>
+ * Copyright (C) 2017 Julian Scheel <julian@...st.de>
  *
  * This driver doesn't expect any real Audio codec to be present
  * on the device - the audio streams are simply sinked to and
@@ -175,16 +176,18 @@ static struct uac1_as_header_descriptor as_in_header_desc = {
 	.wFormatTag =		UAC_FORMAT_TYPE_I_PCM,
 };
 
-DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1);
+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_1 as_out_type_i_desc = {
-	.bLength =		UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
+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 =		1,
+	.bSamFreqType =		0, /* filled on rate setup */
 };
 
 /* Standard ISO OUT Endpoint Descriptor */
@@ -208,14 +211,14 @@ static struct uac_iso_endpoint_descriptor as_iso_out_desc = {
 	.wLockDelay =		cpu_to_le16(1),
 };
 
-static struct uac_format_type_i_discrete_descriptor_1 as_in_type_i_desc = {
-	.bLength =		UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(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 =		1,
+	.bSamFreqType =		0, /* filled on rate setup */
 };
 
 /* Standard ISO OUT Endpoint Descriptor */
@@ -311,23 +314,57 @@ static struct usb_gadget_strings *uac1_strings[] = {
  * This function is an ALSA sound card following USB Audio Class Spec 1.0.
  */
 
+static void uac_cs_attr_sample_rate(struct usb_ep *ep, struct usb_request *req)
+{
+	struct usb_function *fn = ep->driver_data;
+	struct usb_composite_dev *cdev = fn->config->cdev;
+	struct g_audio *agdev = func_to_g_audio(fn);
+	struct f_uac *uac1 = func_to_uac(fn);
+	struct f_uac_opts *opts = g_audio_to_uac_opts(agdev);
+	u8 *buf = (u8 *)req->buf;
+	u32 val = 0;
+
+	if (req->actual != 3) {
+		WARN(cdev, "Invalid data size for UAC_EP_CS_ATTR_SAMPLE_RATE.\n");
+		return;
+	}
+
+	val = buf[0] | (buf[1] << 8) | (buf[2] << 16);
+
+	if (uac1->ctl_id == (USB_DIR_IN | 1)) {
+		opts->p_srate_active = val;
+		u_audio_set_playback_srate(agdev, opts->p_srate_active);
+	} else if (uac1->ctl_id == (USB_DIR_OUT | 1)) {
+		opts->c_srate_active = val;
+		u_audio_set_capture_srate(agdev, opts->c_srate_active);
+	}
+}
+
 static int audio_set_endpoint_req(struct usb_function *f,
 		const struct usb_ctrlrequest *ctrl)
 {
 	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request	*req = f->config->cdev->req;
+	struct f_uac		*uac1 = func_to_uac(f);
 	int			value = -EOPNOTSUPP;
 	u8			ep = le16_to_cpu(ctrl->wIndex) & 0xff;
 	u16			len = le16_to_cpu(ctrl->wLength);
 	u16			w_value = le16_to_cpu(ctrl->wValue);
+	u8			cs = w_value >> 8;
 
 	DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
 			ctrl->bRequest, w_value, len, ep);
 
 	switch (ctrl->bRequest) {
-	case UAC_SET_CUR:
+	case UAC_SET_CUR: {
+		if (cs == UAC_EP_CS_ATTR_SAMPLE_RATE) {
+			cdev->gadget->ep0->driver_data = f;
+			uac1->ctl_id = ep;
+			req->complete = uac_cs_attr_sample_rate;
+		}
 		value = len;
 		break;
-
+		}
 	case UAC_SET_MIN:
 		break;
 
@@ -351,16 +388,34 @@ static int audio_get_endpoint_req(struct usb_function *f,
 		const struct usb_ctrlrequest *ctrl)
 {
 	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request *req = f->config->cdev->req;
+	struct g_audio *agdev = func_to_g_audio(f);
+	struct f_uac_opts *opts = g_audio_to_uac_opts(agdev);
+	u8 *buf = (u8 *)req->buf;
 	int value = -EOPNOTSUPP;
 	u8 ep = le16_to_cpu(ctrl->wIndex) & 0xff;
 	u16 len = le16_to_cpu(ctrl->wLength);
 	u16 w_value = le16_to_cpu(ctrl->wValue);
+	u8 cs = w_value >> 8;
+	u32 val = 0;
 
 	DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
 			ctrl->bRequest, w_value, len, ep);
 
 	switch (ctrl->bRequest) {
-	case UAC_GET_CUR:
+	case UAC_GET_CUR: {
+		if (cs == UAC_EP_CS_ATTR_SAMPLE_RATE) {
+			if (ep == (USB_DIR_IN | 1))
+				val = opts->p_srate_active;
+			else if (ep == (USB_DIR_OUT | 1))
+				val = opts->c_srate_active;
+			buf[2] = (val >> 16) & 0xff;
+			buf[1] = (val >> 8) & 0xff;
+			buf[0] = val & 0xff;
+		}
+		value = len;
+		break;
+		}
 	case UAC_GET_MIN:
 	case UAC_GET_MAX:
 	case UAC_GET_RES:
@@ -507,9 +562,8 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
 	struct f_uac_opts		*audio_opts;
 	struct usb_ep			*ep = NULL;
 	struct usb_string		*us;
-	u8				*sam_freq;
-	int				rate;
 	int				status;
+	int				idx, i;
 
 	audio_opts = container_of(f->fi, struct f_uac_opts, func_inst);
 
@@ -541,12 +595,23 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
 	as_in_type_i_desc.bBitResolution = audio_opts->p_ssize * 8;
 
 	/* Set sample rates */
-	rate = audio_opts->c_srate;
-	sam_freq = as_out_type_i_desc.tSamFreq[0];
-	memcpy(sam_freq, &rate, 3);
-	rate = audio_opts->p_srate;
-	sam_freq = as_in_type_i_desc.tSamFreq[0];
-	memcpy(sam_freq, &rate, 3);
+	for (i = 0, idx = 0; i < UAC_MAX_RATES; i++) {
+		if (audio_opts->c_srate[i] == 0)
+			break;
+		memcpy(as_out_type_i_desc.tSamFreq[idx++],
+				&audio_opts->c_srate[i], 3);
+	}
+	as_out_type_i_desc.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(idx);
+	as_out_type_i_desc.bSamFreqType = idx;
+
+	for (i = 0, idx = 0; i < UAC_MAX_RATES; i++) {
+		if (audio_opts->p_srate[i] == 0)
+			break;
+		memcpy(as_in_type_i_desc.tSamFreq[idx++],
+				&audio_opts->p_srate[i], 3);
+	}
+	as_in_type_i_desc.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(idx);
+	as_in_type_i_desc.bSamFreqType = idx;
 
 	/* allocate instance-specific interface IDs, and patch descriptors */
 	status = usb_interface_id(c, f);
@@ -598,10 +663,14 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
 	audio->out_ep_maxpsize = as_out_ep_desc.wMaxPacketSize;
 	audio->in_ep_maxpsize = as_in_ep_desc.wMaxPacketSize;
 	audio->params.c_chmask = audio_opts->c_chmask;
-	audio->params.c_srate = audio_opts->c_srate;
+	memcpy(audio->params.c_srate, audio_opts->c_srate,
+			sizeof(audio->params.c_srate));
+	audio->params.c_srate_active = audio_opts->c_srate_active;
 	audio->params.c_ssize = audio_opts->c_ssize;
 	audio->params.p_chmask = audio_opts->p_chmask;
-	audio->params.p_srate = audio_opts->p_srate;
+	memcpy(audio->params.p_srate, audio_opts->p_srate,
+			sizeof(audio->params.p_srate));
+	audio->params.p_srate_active = audio_opts->p_srate_active;
 	audio->params.p_ssize = audio_opts->p_ssize;
 	audio->params.req_number = audio_opts->req_number;
 
@@ -625,13 +694,14 @@ static struct configfs_item_operations f_uac1_item_ops = {
 
 
 UAC_ATTRIBUTE(c_chmask);
-UAC_ATTRIBUTE(c_srate);
 UAC_ATTRIBUTE(c_ssize);
 UAC_ATTRIBUTE(p_chmask);
-UAC_ATTRIBUTE(p_srate);
 UAC_ATTRIBUTE(p_ssize);
 UAC_ATTRIBUTE(req_number);
 
+UAC_RATE_ATTRIBUTE(p_srate);
+UAC_RATE_ATTRIBUTE(c_srate);
+
 static struct configfs_attribute *f_uac1_attrs[] = {
 	&f_uac_opts_attr_c_chmask,
 	&f_uac_opts_attr_c_srate,
@@ -672,10 +742,12 @@ static struct usb_function_instance *f_audio_alloc_inst(void)
 				    &f_uac1_func_type);
 
 	opts->c_chmask = UAC_DEF_CCHMASK;
-	opts->c_srate = UAC_DEF_CSRATE;
+	opts->c_srate[0] = UAC_DEF_CSRATE;
+	opts->c_srate_active = UAC_DEF_CSRATE;
 	opts->c_ssize = UAC_DEF_CSSIZE;
 	opts->p_chmask = UAC_DEF_PCHMASK;
-	opts->p_srate = UAC_DEF_PSRATE;
+	opts->p_srate[0] = UAC_DEF_PSRATE;
+	opts->p_srate_active = UAC_DEF_PSRATE;
 	opts->p_ssize = UAC_DEF_PSSIZE;
 	opts->req_number = UAC_DEF_REQ_NUM;
 	return &opts->func_inst;
diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index f5e93e168ec5..7d52a233d301 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -4,6 +4,7 @@
  * Copyright (C) 2011
  *    Yadwinder Singh (yadi.brar01@...il.com)
  *    Jaswinder Singh (jaswinder.singh@...aro.org)
+ * Copyright (C) 2017 Julian Scheel <julian@...st.de>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -64,14 +65,11 @@ enum {
 	STR_AS_IN_ALT1,
 };
 
-static char clksrc_in[8];
-static char clksrc_out[8];
-
 static struct usb_string strings_fn[] = {
 	[STR_ASSOC].s = "Source/Sink",
 	[STR_IF_CTRL].s = "Topology Control",
-	[STR_CLKSRC_IN].s = clksrc_in,
-	[STR_CLKSRC_OUT].s = clksrc_out,
+	[STR_CLKSRC_IN].s = "Input clock",
+	[STR_CLKSRC_OUT].s = "Output clock",
 	[STR_USB_IT].s = "USBH Out",
 	[STR_IO_IT].s = "USBD Out",
 	[STR_USB_OT].s = "USBH In",
@@ -124,7 +122,7 @@ static struct uac_clock_source_descriptor in_clk_src_desc = {
 	.bDescriptorSubtype = UAC2_CLOCK_SOURCE,
 	.bClockID = USB_IN_CLK_ID,
 	.bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
-	.bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
+	.bmControls = (CONTROL_RDWR << CLK_FREQ_CTRL),
 	.bAssocTerminal = 0,
 };
 
@@ -136,7 +134,7 @@ static struct uac_clock_source_descriptor out_clk_src_desc = {
 	.bDescriptorSubtype = UAC2_CLOCK_SOURCE,
 	.bClockID = USB_OUT_CLK_ID,
 	.bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
-	.bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
+	.bmControls = (CONTROL_RDWR << CLK_FREQ_CTRL),
 	.bAssocTerminal = 0,
 };
 
@@ -428,26 +426,43 @@ struct cntrl_cur_lay3 {
 };
 
 struct cntrl_range_lay3 {
-	__u16	wNumSubRanges;
 	__u32	dMIN;
 	__u32	dMAX;
 	__u32	dRES;
 } __packed;
 
+#define ranges_size(c) (sizeof(c.wNumSubRanges) + c.wNumSubRanges \
+		* sizeof(struct cntrl_ranges_lay3))
+struct cntrl_ranges_lay3 {
+	__u16	wNumSubRanges;
+	struct cntrl_range_lay3 r[UAC_MAX_RATES];
+} __packed;
+
 static void set_ep_max_packet_size(const struct f_uac_opts *uac2_opts,
 	struct usb_endpoint_descriptor *ep_desc,
 	unsigned int factor, bool is_playback)
 {
-	int chmask, srate, ssize;
+	int chmask, srate = 0, ssize;
 	u16 max_packet_size;
+	int i;
 
 	if (is_playback) {
 		chmask = uac2_opts->p_chmask;
-		srate = uac2_opts->p_srate;
+		for (i = 0; i < UAC_MAX_RATES; i++) {
+			if (uac2_opts->p_srate[i] == 0)
+				break;
+			if (uac2_opts->p_srate[i] > srate)
+				srate = uac2_opts->p_srate[i];
+		}
 		ssize = uac2_opts->p_ssize;
 	} else {
 		chmask = uac2_opts->c_chmask;
-		srate = uac2_opts->c_srate;
+		for (i = 0; i < UAC_MAX_RATES; i++) {
+			if (uac2_opts->c_srate[i] == 0)
+				break;
+			if (uac2_opts->c_srate[i] > srate)
+				srate = uac2_opts->c_srate[i];
+		}
 		ssize = uac2_opts->c_ssize;
 	}
 
@@ -502,9 +517,6 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
 	as_in_fmt1_desc.bSubslotSize = uac2_opts->p_ssize;
 	as_in_fmt1_desc.bBitResolution = uac2_opts->p_ssize * 8;
 
-	snprintf(clksrc_in, sizeof(clksrc_in), "%uHz", uac2_opts->p_srate);
-	snprintf(clksrc_out, sizeof(clksrc_out), "%uHz", uac2_opts->c_srate);
-
 	ret = usb_interface_id(cfg, fn);
 	if (ret < 0) {
 		dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
@@ -568,10 +580,14 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
 	agdev->gadget = gadget;
 
 	agdev->params.p_chmask = uac2_opts->p_chmask;
-	agdev->params.p_srate = uac2_opts->p_srate;
+	memcpy(agdev->params.p_srate, uac2_opts->p_srate,
+			sizeof(agdev->params.p_srate));
+	agdev->params.p_srate_active = uac2_opts->p_srate_active;
 	agdev->params.p_ssize = uac2_opts->p_ssize;
 	agdev->params.c_chmask = uac2_opts->c_chmask;
-	agdev->params.c_srate = uac2_opts->c_srate;
+	memcpy(agdev->params.c_srate, uac2_opts->c_srate,
+			sizeof(agdev->params.c_srate));
+	agdev->params.c_srate_active = uac2_opts->c_srate_active;
 	agdev->params.c_ssize = uac2_opts->c_ssize;
 	agdev->params.req_number = uac2_opts->req_number;
 	ret = g_audio_setup(agdev, "UAC2 PCM", "UAC2_Gadget");
@@ -677,8 +693,8 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
 	int p_srate, c_srate;
 
 	opts = g_audio_to_uac_opts(agdev);
-	p_srate = opts->p_srate;
-	c_srate = opts->c_srate;
+	p_srate = opts->p_srate_active;
+	c_srate = opts->c_srate_active;
 
 	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
 		struct cntrl_cur_lay3 c;
@@ -689,6 +705,7 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
 		else if (entity_id == USB_OUT_CLK_ID)
 			c.dCUR = c_srate;
 
+		DBG(fn->config->cdev, "%s(): %d\n", __func__, c.dCUR);
 		value = min_t(unsigned, w_length, sizeof c);
 		memcpy(req->buf, &c, value);
 	} else if (control_selector == UAC2_CS_CONTROL_CLOCK_VALID) {
@@ -714,28 +731,40 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
 	u16 w_value = le16_to_cpu(cr->wValue);
 	u8 entity_id = (w_index >> 8) & 0xff;
 	u8 control_selector = w_value >> 8;
-	struct cntrl_range_lay3 r;
+	struct cntrl_ranges_lay3 rs;
 	int value = -EOPNOTSUPP;
-	int p_srate, c_srate;
+	int srate = 0;
+	int i;
 
 	opts = g_audio_to_uac_opts(agdev);
-	p_srate = opts->p_srate;
-	c_srate = opts->c_srate;
 
 	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
-		if (entity_id == USB_IN_CLK_ID)
-			r.dMIN = p_srate;
-		else if (entity_id == USB_OUT_CLK_ID)
-			r.dMIN = c_srate;
-		else
-			return -EOPNOTSUPP;
-
-		r.dMAX = r.dMIN;
-		r.dRES = 0;
-		r.wNumSubRanges = 1;
+		rs.wNumSubRanges = 0;
+		for (i = 0; i < UAC_MAX_RATES; i++) {
+			if (entity_id == USB_IN_CLK_ID)
+				srate = opts->p_srate[i];
+			else if (entity_id == USB_OUT_CLK_ID)
+				srate = opts->c_srate[i];
+			else
+				return -EOPNOTSUPP;
+
+			if (srate == 0)
+				break;
+
+			rs.r[rs.wNumSubRanges].dMIN = srate;
+			rs.r[rs.wNumSubRanges].dMAX = srate;
+			rs.r[rs.wNumSubRanges].dRES = 0;
+			rs.wNumSubRanges++;
+			DBG(fn->config->cdev,
+					"%s(): clk %d: report rate %d. %d\n",
+					__func__, entity_id, rs.wNumSubRanges,
+					srate);
+		}
 
-		value = min_t(unsigned, w_length, sizeof r);
-		memcpy(req->buf, &r, value);
+		value = min_t(unsigned int, w_length, ranges_size(rs));
+		DBG(fn->config->cdev, "%s(): send %d rates, size %d\n",
+				__func__, rs.wNumSubRanges, value);
+		memcpy(req->buf, &rs, value);
 	} else {
 		dev_err(&agdev->gadget->dev,
 			"%s:%d control_selector=%d TODO!\n",
@@ -748,6 +777,7 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
 static int
 ac_rq_in(struct usb_function *fn, const struct usb_ctrlrequest *cr)
 {
+	DBG(fn->config->cdev, "%s(): %d\n", __func__, cr->bRequest);
 	if (cr->bRequest == UAC2_CS_CUR)
 		return in_rq_cur(fn, cr);
 	else if (cr->bRequest == UAC2_CS_RANGE)
@@ -756,15 +786,50 @@ ac_rq_in(struct usb_function *fn, const struct usb_ctrlrequest *cr)
 		return -EOPNOTSUPP;
 }
 
+static void uac2_cs_control_sam_freq(struct usb_ep *ep, struct usb_request *req)
+{
+	struct usb_function *fn = ep->driver_data;
+	struct usb_composite_dev *cdev = fn->config->cdev;
+	struct g_audio *agdev = func_to_g_audio(fn);
+	struct f_uac *uac2 = func_to_uac(fn);
+	struct f_uac_opts *opts = g_audio_to_uac_opts(agdev);
+	u32 val;
+
+	if (req->actual != 4) {
+		WARN(cdev, "Invalid data size for UAC2_CS_CONTROL_SAM_FREQ.\n");
+		return;
+	}
+
+	val = le32_to_cpu(*((u32 *)req->buf));
+	if (uac2->ctl_id == USB_IN_CLK_ID) {
+		opts->p_srate_active = val;
+		u_audio_set_playback_srate(agdev, opts->p_srate_active);
+	} else if (uac2->ctl_id == USB_OUT_CLK_ID) {
+		opts->c_srate_active = val;
+		u_audio_set_capture_srate(agdev, opts->c_srate_active);
+	}
+}
+
 static int
 out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
 {
+	struct usb_composite_dev *cdev = fn->config->cdev;
+	struct usb_request *req = cdev->req;
 	u16 w_length = le16_to_cpu(cr->wLength);
+	struct f_uac *uac2 = func_to_uac(fn);
 	u16 w_value = le16_to_cpu(cr->wValue);
+	u16 w_index = le16_to_cpu(cr->wIndex);
 	u8 control_selector = w_value >> 8;
+	u8 clock_id = w_index >> 8;
 
-	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ)
+	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
+		DBG(cdev, "control_selector UAC2_CS_CONTROL_SAM_FREQ, clock: %d\n",
+				clock_id);
+		cdev->gadget->ep0->driver_data = fn;
+		uac2->ctl_id = clock_id;
+		req->complete = uac2_cs_control_sam_freq;
 		return w_length;
+	}
 
 	return -EOPNOTSUPP;
 }
@@ -829,13 +894,14 @@ static struct configfs_item_operations f_uac2_item_ops = {
 };
 
 UAC_ATTRIBUTE(p_chmask);
-UAC_ATTRIBUTE(p_srate);
 UAC_ATTRIBUTE(p_ssize);
 UAC_ATTRIBUTE(c_chmask);
-UAC_ATTRIBUTE(c_srate);
 UAC_ATTRIBUTE(c_ssize);
 UAC_ATTRIBUTE(req_number);
 
+UAC_RATE_ATTRIBUTE(p_srate);
+UAC_RATE_ATTRIBUTE(c_srate);
+
 static struct configfs_attribute *f_uac2_attrs[] = {
 	&f_uac_opts_attr_p_chmask,
 	&f_uac_opts_attr_p_srate,
@@ -876,10 +942,12 @@ static struct usb_function_instance *afunc_alloc_inst(void)
 				    &f_uac2_func_type);
 
 	opts->p_chmask = UAC_DEF_PCHMASK;
-	opts->p_srate = UAC_DEF_PSRATE;
+	opts->p_srate[0] = UAC_DEF_PSRATE;
+	opts->p_srate_active = UAC_DEF_PSRATE;
 	opts->p_ssize = UAC_DEF_PSSIZE;
 	opts->c_chmask = UAC_DEF_CCHMASK;
-	opts->c_srate = UAC_DEF_CSRATE;
+	opts->c_srate[0] = UAC_DEF_CSRATE;
+	opts->c_srate_active = UAC_DEF_CSRATE;
 	opts->c_ssize = UAC_DEF_CSSIZE;
 	opts->req_number = UAC_DEF_REQ_NUM;
 	return &opts->func_inst;
diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c
index bf4da4361b6d..6aef4aa9b18f 100644
--- a/drivers/usb/gadget/function/u_audio.c
+++ b/drivers/usb/gadget/function/u_audio.c
@@ -22,6 +22,7 @@
  */
 
 #include <linux/module.h>
+#include <sound/control.h>
 #include <sound/core.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
@@ -286,18 +287,17 @@ static int uac_pcm_open(struct snd_pcm_substream *substream)
 {
 	struct snd_uac_chip *uac = snd_pcm_substream_chip(substream);
 	struct snd_pcm_runtime *runtime = substream->runtime;
-	struct g_audio *audio_dev;
+	struct g_audio *audio_dev = uac->audio_dev;
 	struct uac_params *params;
 	int p_ssize, c_ssize;
 	int p_srate, c_srate;
 	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_srate = params->p_srate;
-	c_srate = params->c_srate;
+	p_srate = params->p_srate_active;
+	c_srate = params->c_srate_active;
 	p_chmask = params->p_chmask;
 	c_chmask = params->c_chmask;
 	uac->p_residue = 0;
@@ -348,6 +348,52 @@ static int uac_pcm_open(struct snd_pcm_substream *substream)
 	return 0;
 }
 
+static int uac_pcm_rate_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 324000;
+	return 0;
+}
+
+static int uac_pcm_rate_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_uac_chip *uac = snd_kcontrol_chip(kcontrol);
+	struct g_audio *audio_dev = uac->audio_dev;
+	struct uac_params *params = &audio_dev->params;
+
+	if (kcontrol->private_value == SNDRV_PCM_STREAM_CAPTURE)
+		ucontrol->value.integer.value[0] = params->c_srate_active;
+	else if (kcontrol->private_value == SNDRV_PCM_STREAM_PLAYBACK)
+		ucontrol->value.integer.value[0] = params->p_srate_active;
+	else
+		return -EINVAL;
+
+	return 0;
+}
+
+static struct snd_kcontrol_new uac_pcm_controls[] = {
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+	.name = "Capture Rate",
+	.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info = uac_pcm_rate_info,
+	.get = uac_pcm_rate_get,
+	.private_value = SNDRV_PCM_STREAM_CAPTURE,
+},
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+	.name = "Playback Rate",
+	.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+	.info = uac_pcm_rate_info,
+	.get = uac_pcm_rate_get,
+	.private_value = SNDRV_PCM_STREAM_PLAYBACK,
+},
+};
+
 /* ALSA cries without these function pointers */
 static int uac_pcm_null(struct snd_pcm_substream *substream)
 {
@@ -392,6 +438,59 @@ static inline void free_ep(struct uac_rtd_params *prm, struct usb_ep *ep)
 		dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__);
 }
 
+static struct snd_kcontrol *u_audio_get_ctl(struct g_audio *audio_dev,
+		const char *name)
+{
+	struct snd_ctl_elem_id elem_id;
+
+	memset(&elem_id, 0, sizeof(elem_id));
+	elem_id.iface = SNDRV_CTL_ELEM_IFACE_PCM;
+	strcpy(elem_id.name, name);
+	return snd_ctl_find_id(audio_dev->uac->card, &elem_id);
+}
+
+int u_audio_set_capture_srate(struct g_audio *audio_dev, int srate)
+{
+	struct snd_kcontrol *ctl = u_audio_get_ctl(audio_dev, "Capture Rate");
+	struct uac_params *params = &audio_dev->params;
+	int i;
+
+	for (i = 0; i < UAC_MAX_RATES; i++) {
+		if (params->c_srate[i] == srate) {
+			params->c_srate_active = srate;
+			snd_ctl_notify(audio_dev->uac->card,
+					SNDRV_CTL_EVENT_MASK_VALUE, &ctl->id);
+			return 0;
+		}
+		if (params->c_srate[i] == 0)
+			break;
+	}
+
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(u_audio_set_capture_srate);
+
+int u_audio_set_playback_srate(struct g_audio *audio_dev, int srate)
+{
+	struct snd_kcontrol *ctl = u_audio_get_ctl(audio_dev, "Playback Rate");
+	struct uac_params *params = &audio_dev->params;
+	int i;
+
+	for (i = 0; i < UAC_MAX_RATES; i++) {
+		if (params->p_srate[i] == srate) {
+			params->p_srate_active = srate;
+			snd_ctl_notify(audio_dev->uac->card,
+					SNDRV_CTL_EVENT_MASK_VALUE, &ctl->id);
+			return 0;
+		}
+		if (params->p_srate[i] == 0)
+			break;
+	}
+
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(u_audio_set_playback_srate);
+
 int u_audio_start_capture(struct g_audio *audio_dev)
 {
 	struct snd_uac_chip *uac = audio_dev->uac;
@@ -456,6 +555,7 @@ int u_audio_start_playback(struct g_audio *audio_dev)
 	const struct usb_endpoint_descriptor *ep_desc;
 	int req_len, i;
 
+	dev_dbg(dev, "start playback with rate %d\n", params->p_srate_active);
 	ep = audio_dev->in_ep;
 	prm = &uac->p_prm;
 	config_ep_by_speed(gadget, &audio_dev->func, ep);
@@ -471,7 +571,7 @@ int u_audio_start_playback(struct g_audio *audio_dev)
 	/* pre-compute some values for iso_complete() */
 	uac->p_framesize = params->p_ssize *
 			    num_channels(params->p_chmask);
-	rate = params->p_srate * uac->p_framesize;
+	rate = params->p_srate_active * uac->p_framesize;
 	uac->p_interval = factor / (1 << (ep_desc->bInterval - 1));
 	uac->p_pktsize = min_t(unsigned int, rate / uac->p_interval,
 				prm->max_psize);
@@ -528,6 +628,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
 	struct uac_params *params;
 	int p_chmask, c_chmask;
 	int err;
+	int i;
 
 	if (!g_audio)
 		return -EINVAL;
@@ -617,6 +718,14 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
 	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
 		snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX);
 
+	/* Add controls */
+	for (i = 0; i < ARRAY_SIZE(uac_pcm_controls); i++) {
+		err = snd_ctl_add(card,
+				snd_ctl_new1(&uac_pcm_controls[i], uac));
+		if (err < 0)
+			goto snd_fail;
+	}
+
 	err = snd_card_register(card);
 
 	if (!err)
diff --git a/drivers/usb/gadget/function/u_audio.h b/drivers/usb/gadget/function/u_audio.h
index 07e13784cbb8..3ba500a322dc 100644
--- a/drivers/usb/gadget/function/u_audio.h
+++ b/drivers/usb/gadget/function/u_audio.h
@@ -21,15 +21,18 @@
 
 #include <linux/usb/composite.h>
 
+#define UAC_MAX_RATES 10
 struct uac_params {
 	/* playback */
 	int p_chmask;	/* channel mask */
-	int p_srate;	/* rate in Hz */
+	int p_srate[UAC_MAX_RATES];	/* rate in Hz */
+	int p_srate_active;		/* selected rate in Hz */
 	int p_ssize;	/* sample size */
 
 	/* capture */
 	int c_chmask;	/* channel mask */
-	int c_srate;	/* rate in Hz */
+	int c_srate[UAC_MAX_RATES];	/* rate in Hz */
+	int c_srate_active;		/* selected rate in Hz */
 	int c_ssize;	/* sample size */
 
 	int req_number; /* number of preallocated requests */
@@ -91,5 +94,7 @@ int u_audio_start_capture(struct g_audio *g_audio);
 void u_audio_stop_capture(struct g_audio *g_audio);
 int u_audio_start_playback(struct g_audio *g_audio);
 void u_audio_stop_playback(struct g_audio *g_audio);
+int u_audio_set_capture_srate(struct g_audio *audio_dev, int srate);
+int u_audio_set_playback_srate(struct g_audio *audio_dev, int srate);
 
 #endif /* __U_AUDIO_H */
diff --git a/drivers/usb/gadget/function/u_uac.h b/drivers/usb/gadget/function/u_uac.h
index b0bc811b1ffb..8721f8f2d931 100644
--- a/drivers/usb/gadget/function/u_uac.h
+++ b/drivers/usb/gadget/function/u_uac.h
@@ -13,6 +13,7 @@
 #define __U_UAC_H
 
 #include <linux/usb/composite.h>
+#include "u_audio.h"
 
 #define UAC_DEF_CCHMASK		0x3
 #define UAC_DEF_CSRATE		48000
@@ -27,10 +28,12 @@
 struct f_uac_opts {
 	struct usb_function_instance	func_inst;
 	int				c_chmask;
-	int				c_srate;
+	int				c_srate[UAC_MAX_RATES];
+	int				c_srate_active;
 	int				c_ssize;
 	int				p_chmask;
-	int				p_srate;
+	int				p_srate[UAC_MAX_RATES];
+	int				p_srate_active;
 	int				p_ssize;
 	int				req_number;
 	unsigned			bound:1;
@@ -82,10 +85,71 @@ end:									\
 									\
 CONFIGFS_ATTR(f_uac_opts_, name)
 
+#define UAC_RATE_ATTRIBUTE(name)					\
+static ssize_t f_uac_opts_##name##_show(struct config_item *item,	\
+					 char *page)			\
+{									\
+	struct f_uac_opts *opts = to_f_uac_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[i] == 0)					\
+			continue;					\
+		result += sprintf(page + strlen(page), "%u,",		\
+				opts->name[i]);				\
+	}								\
+	if (strlen(page) > 0)						\
+		page[strlen(page) - 1] = '\n';				\
+	mutex_unlock(&opts->lock);					\
+									\
+	return result;							\
+}									\
+									\
+static ssize_t f_uac_opts_##name##_store(struct config_item *item,	\
+					  const char *page, size_t len)	\
+{									\
+	struct f_uac_opts *opts = to_f_uac_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, 0x00, sizeof(opts->name));			\
+	split_page = kstrdup(page, GFP_KERNEL);				\
+	while ((token = strsep(&split_page, ",")) != NULL) {		\
+		ret = kstrtou32(token, 0, &num);			\
+		if (ret)						\
+			goto end;					\
+									\
+		opts->name[i++] = num;					\
+		opts->name##_active = num;				\
+		ret = len;						\
+	};								\
+									\
+end:									\
+	kfree(split_page);						\
+	mutex_unlock(&opts->lock);					\
+	return ret;							\
+}									\
+									\
+CONFIGFS_ATTR(f_uac_opts_, name)
+
 struct f_uac {
 	struct g_audio g_audio;
 	u8 ac_intf, as_in_intf, as_out_intf;
 	u8 ac_alt, as_in_alt, as_out_alt;	/* needed for get_alt() */
+	int ctl_id;
 };
 
 static inline struct f_uac *func_to_uac(struct usb_function *f)
-- 
2.13.2

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ