From patchwork Sun Nov 8 00:18:29 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ruslan Bilovol X-Patchwork-Id: 11889351 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 7E30614B4 for ; Sun, 8 Nov 2020 00:18:38 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 5877620B1F for ; Sun, 8 Nov 2020 00:18:38 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="YFYXxNuP" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726206AbgKHASh (ORCPT ); Sat, 7 Nov 2020 19:18:37 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47916 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726043AbgKHASh (ORCPT ); Sat, 7 Nov 2020 19:18:37 -0500 Received: from mail-lf1-x141.google.com (mail-lf1-x141.google.com [IPv6:2a00:1450:4864:20::141]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 04CB4C0613CF for ; Sat, 7 Nov 2020 16:18:37 -0800 (PST) Received: by mail-lf1-x141.google.com with SMTP id e27so7149865lfn.7 for ; Sat, 07 Nov 2020 16:18:36 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=KKMsswpCnGv0Vxj+BpdUjM//042q2GbNKi3RGaF32eI=; b=YFYXxNuP+9YlCC93y7x9pIC3l22rVlapty4gPilpimbR83rheXwvP5usMvxHagMmnp 5fEXgkM2Euy5waL0eaPd4J9R4/TGkHRjPhS0oRJ4y0N9lFSNGyPpsB2Kx8BtRi++lMaS +qI5SHuKV94bvdRAC/Bw9ZkUTOsmUGVVKXYnHpQwhApbfTHCMPGNElZLnw+8u/Cynicn O9SN7dEPjlfRUKvyDn5VUrRQIq6+hvzDC9LB7w6RIJC3BkYaDO7DiXYsbKscDJ0tHrZp Mh3w7gW/5rYROyvSWGUmBovoRqCBW+6N1fa0CgRryAHi/d1J4wugz9UOQRWOQYhUk4mz +XWA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=KKMsswpCnGv0Vxj+BpdUjM//042q2GbNKi3RGaF32eI=; b=IAbCNeSE6MjFk/XSAyC39yIzSAvoIQfRJWjjI8ywc0aTztHoeRBlveVyU0N5Rq1MVV 9kHXgPNwISh6M8cGXkhQB8IKYGRKVUohlQPGrGEkFLEasm2hKYiX/emkMii5soQLYNq+ 8FGvgwXEBzM0CkqU283NuFkf79+a31akwUgzo7FSVJH5+TFjTWy9fzDFzOJV78mAO2wR BtJXIJgPrutNQ+nTDvovz1+h8bLVYvrk2trJruIqPTp8BxyZKH14K+UNJWHEq8xk5JNa 3G0P76lGDQFFc001906oaHqbLOI0AFHj0OQxNB4bHP20LNEbkoOGm/hQfcQEeHR7Ppql mbtg== X-Gm-Message-State: AOAM532KopKynE8SWlAkwyvd0o4Hfep3ShC5vAO3TTYBB4QrTgMsCGP7 mKor2GKD4Bm1+7d6DL6/78dG2/s47ro= X-Google-Smtp-Source: ABdhPJzO+5xyL8Zdpw6O0P3W3J7TkDGljdTTdavtIB+ot0oEKWfAY7xubTY1CTuteKJzJH33oBqm3A== X-Received: by 2002:a05:6512:304b:: with SMTP id b11mr3370679lfb.351.1604794715478; Sat, 07 Nov 2020 16:18:35 -0800 (PST) Received: from localhost ([80.64.86.40]) by smtp.gmail.com with ESMTPSA id y3sm880372ljc.131.2020.11.07.16.18.34 (version=TLS1_2 cipher=AES128-SHA bits=128/128); Sat, 07 Nov 2020 16:18:34 -0800 (PST) From: Ruslan Bilovol To: balbi@kernel.org Cc: linux-usb@vger.kernel.org, gschmottlach@gmail.com Subject: [PATCH 1/3] usb: gadget: f_uac2/u_audio: add feedback endpoint support Date: Sun, 8 Nov 2020 02:18:29 +0200 Message-Id: <1604794711-8661-2-git-send-email-ruslan.bilovol@gmail.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1604794711-8661-1-git-send-email-ruslan.bilovol@gmail.com> References: <1604794711-8661-1-git-send-email-ruslan.bilovol@gmail.com> Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org As per USB and UAC2 specs, asynchronous audio sink endpoint requires explicit synchronization mechanism (Isochronous Feedback Endpoint) Implement feedback companion endpoint for ISO OUT endpoint This patch adds all required infrastructure and USB requests handling for feedback endpoint. Syncrhonization itself is still dummy (feedback ep always reports 'nomimal frequency' e.g. no adjustement is needed). This satisfies hosts that require feedback endpoint (like Win10) and poll it periodically Actual synchronization mechanism should be implemented separately Signed-off-by: Ruslan Bilovol --- drivers/usb/gadget/function/f_uac2.c | 34 +++++++++- drivers/usb/gadget/function/u_audio.c | 124 +++++++++++++++++++++++++++++++++- drivers/usb/gadget/function/u_audio.h | 3 + 3 files changed, 158 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index fb9b875..a57bf77 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -237,7 +237,7 @@ enum { .bDescriptorType = USB_DT_INTERFACE, .bAlternateSetting = 1, - .bNumEndpoints = 1, + .bNumEndpoints = 2, .bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, .bInterfaceProtocol = UAC_VERSION_2, @@ -296,6 +296,27 @@ enum { .wLockDelay = 0, }; +/* STD AS ISO IN Feedback Endpoint */ +static struct usb_endpoint_descriptor fs_epin_fback_desc = { + .bLength = USB_DT_ENDPOINT_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, +}; + +static struct usb_endpoint_descriptor hs_epin_fback_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_USAGE_FEEDBACK, + .wMaxPacketSize = cpu_to_le16(4), + .bInterval = 4, +}; + + /* Audio Streaming IN Interface - Alt0 */ static struct usb_interface_descriptor std_as_in_if0_desc = { .bLength = sizeof std_as_in_if0_desc, @@ -392,6 +413,7 @@ enum { (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, @@ -422,6 +444,7 @@ enum { (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, @@ -541,6 +564,7 @@ static void setup_descriptor(struct f_uac2_opts *opts) fs_audio_desc[i++] = USBDHDR(&as_out_fmt1_desc); fs_audio_desc[i++] = USBDHDR(&fs_epout_desc); fs_audio_desc[i++] = USBDHDR(&as_iso_out_desc); + fs_audio_desc[i++] = USBDHDR(&fs_epin_fback_desc); } if (EPIN_EN(opts)) { fs_audio_desc[i++] = USBDHDR(&std_as_in_if0_desc); @@ -574,6 +598,7 @@ static void setup_descriptor(struct f_uac2_opts *opts) hs_audio_desc[i++] = USBDHDR(&as_out_fmt1_desc); hs_audio_desc[i++] = USBDHDR(&hs_epout_desc); hs_audio_desc[i++] = USBDHDR(&as_iso_out_desc); + hs_audio_desc[i++] = USBDHDR(&hs_epin_fback_desc); } if (EPIN_EN(opts)) { hs_audio_desc[i++] = USBDHDR(&std_as_in_if0_desc); @@ -681,6 +706,12 @@ static void setup_descriptor(struct f_uac2_opts *opts) dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return -ENODEV; } + 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__); + return -ENODEV; + } } if (EPIN_EN(uac2_opts)) { @@ -699,6 +730,7 @@ static void setup_descriptor(struct f_uac2_opts *opts) le16_to_cpu(hs_epout_desc.wMaxPacketSize)); 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; setup_descriptor(uac2_opts); diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c index e6d32c5..33c9288 100644 --- a/drivers/usb/gadget/function/u_audio.c +++ b/drivers/usb/gadget/function/u_audio.c @@ -43,6 +43,10 @@ struct uac_rtd_params { unsigned int max_psize; /* MaxPacketSize of endpoint */ struct uac_req *ureq; + struct usb_request *req_fback; /* Feedback endpoint request */ + unsigned int ffback; /* Real frequency reported by feedback endpoint */ + bool fb_ep_enabled; /* if the ep is enabled */ + spinlock_t lock; }; @@ -76,6 +80,35 @@ struct snd_uac_chip { .periods_min = MIN_PERIODS, }; +static void u_audio_set_fback_frequency(enum usb_device_speed speed, + unsigned int freq, void *buf) +{ + u32 ff = 0; + + if (speed == USB_SPEED_FULL) { + /* + * Full-speed feedback endpoints report frequency + * in samples/microframe + * Format is encoded in Q10.10 left-justified in the 24 bits, + * so that it has a Q10.14 format. + */ + ff = DIV_ROUND_UP((freq << 14), 1000); + } else { + /* + * High-speed feedback endpoints report frequency + * in samples/microframe. + * Format is encoded in Q12.13 fitted into four bytes so that + * the binary point is located between the second and the third + * byte fromat (that is Q16.16) + * + * Prevent integer overflow by calculating in Q12.13 fromat and + * then shifting to Q16.16 + */ + ff = DIV_ROUND_UP((freq << 13), (8*1000)) << 3; + } + *(__le32 *)buf = cpu_to_le32(ff); +} + static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req) { unsigned int pending; @@ -182,6 +215,36 @@ static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req) dev_err(uac->card->dev, "%d Error!\n", __LINE__); } +static void u_audio_iso_fback_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct uac_rtd_params *prm = req->context; + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev = uac->audio_dev; + int status = req->status; + unsigned long flags; + + /* i/f shutting down */ + if (!prm->fb_ep_enabled || req->status == -ESHUTDOWN) + return; + + /* + * We can't really do much about bad xfers. + * Afterall, the ISOCH xfers could fail legitimately. + */ + if (status) + pr_debug("%s: iso_complete status(%d) %d/%d\n", + __func__, status, req->actual, req->length); + + spin_lock_irqsave(&prm->lock, flags); + u_audio_set_fback_frequency(audio_dev->gadget->speed, + prm->ffback, req->buf); + spin_unlock_irqrestore(&prm->lock, flags); + + if (usb_ep_queue(ep, req, GFP_ATOMIC)) + dev_err(uac->card->dev, "%d Error!\n", __LINE__); +} + static int uac_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); @@ -346,14 +409,33 @@ 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 inline void free_ep_fback(struct uac_rtd_params *prm, struct usb_ep *ep) +{ + struct snd_uac_chip *uac = prm->uac; + + if (!prm->fb_ep_enabled) + return; + + prm->fb_ep_enabled = false; + + if (prm->req_fback) { + usb_ep_dequeue(ep, prm->req_fback); + kfree(prm->req_fback->buf); + usb_ep_free_request(ep, prm->req_fback); + prm->req_fback = NULL; + } + + if (usb_ep_disable(ep)) + dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__); +} int u_audio_start_capture(struct g_audio *audio_dev) { struct snd_uac_chip *uac = audio_dev->uac; struct usb_gadget *gadget = audio_dev->gadget; struct device *dev = &gadget->dev; - struct usb_request *req; - struct usb_ep *ep; + struct usb_request *req, *req_fback; + struct usb_ep *ep, *ep_fback; struct uac_rtd_params *prm; struct uac_params *params = &audio_dev->params; int req_len, i; @@ -386,6 +468,42 @@ int u_audio_start_capture(struct g_audio *audio_dev) dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); } + ep_fback = audio_dev->in_ep_fback; + if (!ep_fback) + return 0; + + /* Setup feedback endpoint */ + config_ep_by_speed(gadget, &audio_dev->func, ep_fback); + prm->fb_ep_enabled = true; + usb_ep_enable(ep_fback); + req_len = ep_fback->maxpacket; + + req_fback = usb_ep_alloc_request(ep_fback, GFP_ATOMIC); + if (req_fback == NULL) + return -ENOMEM; + + prm->req_fback = req_fback; + req_fback->zero = 0; + req_fback->context = prm; + req_fback->length = req_len; + req_fback->complete = u_audio_iso_fback_complete; + + req_fback->buf = kzalloc(req_len, GFP_ATOMIC); + if (!req_fback->buf) + return -ENOMEM; + + /* + * Configure the feedback endpoint's reported frequency. + * Always start with original frequency since its deviation can't + * be meauserd at start of playback + */ + prm->ffback = params->c_srate; + u_audio_set_fback_frequency(audio_dev->gadget->speed, + prm->ffback, req_fback->buf); + + if (usb_ep_queue(ep_fback, req_fback, GFP_ATOMIC)) + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return 0; } EXPORT_SYMBOL_GPL(u_audio_start_capture); @@ -394,6 +512,8 @@ void u_audio_stop_capture(struct g_audio *audio_dev) { struct snd_uac_chip *uac = audio_dev->uac; + if (audio_dev->in_ep_fback) + free_ep_fback(&uac->c_prm, audio_dev->in_ep_fback); free_ep(&uac->c_prm, audio_dev->out_ep); } EXPORT_SYMBOL_GPL(u_audio_stop_capture); diff --git a/drivers/usb/gadget/function/u_audio.h b/drivers/usb/gadget/function/u_audio.h index 5ea6b86..53e6baf 100644 --- a/drivers/usb/gadget/function/u_audio.h +++ b/drivers/usb/gadget/function/u_audio.h @@ -30,7 +30,10 @@ struct g_audio { struct usb_gadget *gadget; struct usb_ep *in_ep; + struct usb_ep *out_ep; + /* feedback IN endpoint corresponding to out_ep */ + struct usb_ep *in_ep_fback; /* Max packet size for all in_ep possible speeds */ unsigned int in_ep_maxpsize; From patchwork Sun Nov 8 00:18:30 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ruslan Bilovol X-Patchwork-Id: 11889353 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 3A71914B4 for ; Sun, 8 Nov 2020 00:18:40 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 1D8232151B for ; Sun, 8 Nov 2020 00:18:40 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="hcWfc+zz" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726455AbgKHASj (ORCPT ); Sat, 7 Nov 2020 19:18:39 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47922 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726043AbgKHASj (ORCPT ); Sat, 7 Nov 2020 19:18:39 -0500 Received: from mail-lf1-x131.google.com (mail-lf1-x131.google.com [IPv6:2a00:1450:4864:20::131]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B2605C0613CF for ; Sat, 7 Nov 2020 16:18:38 -0800 (PST) Received: by mail-lf1-x131.google.com with SMTP id d17so3778025lfq.10 for ; Sat, 07 Nov 2020 16:18:38 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=hs/n4wHqAZumQHAbTgr2kPhT2sOoR/BpHta+kZDd6TA=; b=hcWfc+zz+ZWnlwaYAydKAS07n0Emka85xI2pP/JKWrrNeGwejHo9jQDuRIn/PqFThE c/Z07hK9gZ63fPsVz+/2WTG73sUTlW5A083KAewID1X/NS7dj8MwFI40igqVXwq8h4j1 c5sljCzI7DayubR9zU63gEUREF9IDXZ71Xfx1LtZACEYhbmXcBZyWRnNboIsO85sNC62 my7gTRfZ1Z7nP4vMhK55lcmaZqFqaIDz7aae/KKM6IQxixw0+gPar0WKf7TKb24i2ZkC uqCKZYPLsMiQczn7ESX1AQ3TNeY4eQQE7QCr64j3xvA8zVNPYi3fYccK084irA4/tXFf /2vg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=hs/n4wHqAZumQHAbTgr2kPhT2sOoR/BpHta+kZDd6TA=; b=DYsZmx3wMpF5JRgpCT4dQt1br+sBGEPuBgCIcb4MzZ9VSlkviSOpQov1hneqUSLIj6 eiQf2AoWz0tOEvXvCjM62vWQjDQQOyeUEqGauuVU12neCthzWbOJCVs0Zo60hquB7euV Dkl46A37LHXviUNCYmmMRNBHWOly1HU2VrHJbiTwd9RHPpXQaiwUvOClNcoNtI9lxNdU 3cPkOx/kbLEgbqJVgf9gJ5/t+RUinsRNlHhqD8Wjpa4l+CRi4nKnnE4ZwyBNhSpW456f bQiE+u3tIyIl+GydqoLlqFXiqem0+4DhQAdyUAxIlClMPbJ8wUkdyUMtUUpWLOrdh42u NqUw== X-Gm-Message-State: AOAM533kcyMWVHUcP2gbkTA9bnQ04AePhwyIuC0Eo45npRV9CchiwG5G 0Qa+iuymePrcY0Ov1KjZs7w= X-Google-Smtp-Source: ABdhPJwmy1cx11+3LfSwf5WXy7lu/szV9XZrB/UGjDjmNO00PZTv6/6LaHR7ni+FCtXvz9Zeog5qDg== X-Received: by 2002:a19:6541:: with SMTP id c1mr3554492lfj.183.1604794717191; Sat, 07 Nov 2020 16:18:37 -0800 (PST) Received: from localhost ([80.64.86.40]) by smtp.gmail.com with ESMTPSA id j19sm891155ljb.51.2020.11.07.16.18.36 (version=TLS1_2 cipher=AES128-SHA bits=128/128); Sat, 07 Nov 2020 16:18:36 -0800 (PST) From: Ruslan Bilovol To: balbi@kernel.org Cc: linux-usb@vger.kernel.org, gschmottlach@gmail.com Subject: [PATCH 2/3] usb: gadget: f_uac2: add adaptive sync support for capture Date: Sun, 8 Nov 2020 02:18:30 +0200 Message-Id: <1604794711-8661-3-git-send-email-ruslan.bilovol@gmail.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1604794711-8661-1-git-send-email-ruslan.bilovol@gmail.com> References: <1604794711-8661-1-git-send-email-ruslan.bilovol@gmail.com> Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org Current f_uac2 USB OUT (aka 'capture') synchronization implements 'ASYNC' scenario which means USB Gadget has it's own freerunning clock and can update Host about real clock frequency through feedback endpoint so Host can align number of samples sent to the USB gadget to prevent overruns/underruns In case if Gadget can has no it's internal clock and can consume audio samples at any rate (for example, on the Gadget side someone records audio directly to a file, or audio samples are played through an external DAC as soon as they arrive), UAC2 spec suggests 'ADAPTIVE' synchronization type. Change UAC2 driver to make it configurable through additional 'c_sync' configfs file. Default remains 'asynchronous' with possibility to switch it to 'adaptive' Signed-off-by: Ruslan Bilovol --- Documentation/ABI/testing/configfs-usb-gadget-uac2 | 1 + Documentation/usb/gadget-testing.rst | 1 + drivers/usb/gadget/function/f_uac2.c | 96 ++++++++++++++++++++-- drivers/usb/gadget/function/u_uac2.h | 2 + 4 files changed, 91 insertions(+), 9 deletions(-) diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac2 b/Documentation/ABI/testing/configfs-usb-gadget-uac2 index 2bfdd4e..4fbff96 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uac2 +++ b/Documentation/ABI/testing/configfs-usb-gadget-uac2 @@ -7,6 +7,7 @@ Description: c_chmask - capture channel mask c_srate - capture sampling rate c_ssize - capture sample size (bytes) + c_sync - capture synchronization type (async/adaptive) p_chmask - playback channel mask p_srate - playback sampling rate p_ssize - playback sample size (bytes) diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index 2eeb3e9..360a7ca 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -728,6 +728,7 @@ The uac2 function provides these attributes in its function directory: c_chmask capture channel mask c_srate capture sampling rate c_ssize capture sample size (bytes) + c_sync capture synchronization type (async/adaptive) p_chmask playback channel mask p_srate playback sampling rate p_ssize playback sample size (bytes) diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index a57bf77..3187ad3 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -41,6 +41,7 @@ #define EPIN_EN(_opts) ((_opts)->p_chmask != 0) #define EPOUT_EN(_opts) ((_opts)->c_chmask != 0) +#define EPOUT_FBACK_IN_EN(_opts) ((_opts)->c_sync == USB_ENDPOINT_SYNC_ASYNC) struct f_uac2 { struct g_audio g_audio; @@ -237,7 +238,7 @@ enum { .bDescriptorType = USB_DT_INTERFACE, .bAlternateSetting = 1, - .bNumEndpoints = 2, + .bNumEndpoints = 1, .bInterfaceClass = USB_CLASS_AUDIO, .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, .bInterfaceProtocol = UAC_VERSION_2, @@ -270,7 +271,7 @@ enum { .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_OUT, - .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, .wMaxPacketSize = cpu_to_le16(1023), .bInterval = 1, }; @@ -279,7 +280,7 @@ enum { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, - .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, .wMaxPacketSize = cpu_to_le16(1024), .bInterval = 1, }; @@ -540,6 +541,19 @@ static void setup_descriptor(struct f_uac2_opts *opts) len += sizeof(io_out_ot_desc); ac_hdr_desc.wTotalLength = cpu_to_le16(len); iad_desc.bInterfaceCount++; + + if (EPOUT_FBACK_IN_EN(opts)) { + fs_epout_desc.bmAttributes |= + USB_ENDPOINT_SYNC_ASYNC; + hs_epout_desc.bmAttributes |= + USB_ENDPOINT_SYNC_ASYNC; + std_as_out_if1_desc.bNumEndpoints++; + } else { + fs_epout_desc.bmAttributes |= + USB_ENDPOINT_SYNC_ADAPTIVE; + hs_epout_desc.bmAttributes |= + USB_ENDPOINT_SYNC_ADAPTIVE; + } } i = 0; @@ -564,7 +578,8 @@ static void setup_descriptor(struct f_uac2_opts *opts) fs_audio_desc[i++] = USBDHDR(&as_out_fmt1_desc); fs_audio_desc[i++] = USBDHDR(&fs_epout_desc); fs_audio_desc[i++] = USBDHDR(&as_iso_out_desc); - fs_audio_desc[i++] = USBDHDR(&fs_epin_fback_desc); + if (EPOUT_FBACK_IN_EN(opts)) + fs_audio_desc[i++] = USBDHDR(&fs_epin_fback_desc); } if (EPIN_EN(opts)) { fs_audio_desc[i++] = USBDHDR(&std_as_in_if0_desc); @@ -598,7 +613,8 @@ static void setup_descriptor(struct f_uac2_opts *opts) hs_audio_desc[i++] = USBDHDR(&as_out_fmt1_desc); hs_audio_desc[i++] = USBDHDR(&hs_epout_desc); hs_audio_desc[i++] = USBDHDR(&as_iso_out_desc); - hs_audio_desc[i++] = USBDHDR(&hs_epin_fback_desc); + if (EPOUT_FBACK_IN_EN(opts)) + hs_audio_desc[i++] = USBDHDR(&hs_epin_fback_desc); } if (EPIN_EN(opts)) { hs_audio_desc[i++] = USBDHDR(&std_as_in_if0_desc); @@ -706,11 +722,14 @@ static void setup_descriptor(struct f_uac2_opts *opts) dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return -ENODEV; } - agdev->in_ep_fback = usb_ep_autoconfig(gadget, + if (EPOUT_FBACK_IN_EN(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__); - return -ENODEV; + if (!agdev->in_ep_fback) { + dev_err(dev, "%s:%d Error!\n", + __func__, __LINE__); + return -ENODEV; + } } } @@ -1057,11 +1076,68 @@ static void f_uac2_attr_release(struct config_item *item) \ CONFIGFS_ATTR(f_uac2_opts_, 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) + UAC2_ATTRIBUTE(p_chmask); UAC2_ATTRIBUTE(p_srate); UAC2_ATTRIBUTE(p_ssize); UAC2_ATTRIBUTE(c_chmask); UAC2_ATTRIBUTE(c_srate); +UAC2_ATTRIBUTE_SYNC(c_sync); UAC2_ATTRIBUTE(c_ssize); UAC2_ATTRIBUTE(req_number); @@ -1072,6 +1148,7 @@ static void f_uac2_attr_release(struct config_item *item) &f_uac2_opts_attr_c_chmask, &f_uac2_opts_attr_c_srate, &f_uac2_opts_attr_c_ssize, + &f_uac2_opts_attr_c_sync, &f_uac2_opts_attr_req_number, NULL, }; @@ -1110,6 +1187,7 @@ static struct usb_function_instance *afunc_alloc_inst(void) opts->c_chmask = UAC2_DEF_CCHMASK; opts->c_srate = UAC2_DEF_CSRATE; opts->c_ssize = UAC2_DEF_CSSIZE; + opts->c_sync = UAC2_DEF_CSYNC; opts->req_number = UAC2_DEF_REQ_NUM; return &opts->func_inst; } diff --git a/drivers/usb/gadget/function/u_uac2.h b/drivers/usb/gadget/function/u_uac2.h index b503571..13589c3 100644 --- a/drivers/usb/gadget/function/u_uac2.h +++ b/drivers/usb/gadget/function/u_uac2.h @@ -21,6 +21,7 @@ #define UAC2_DEF_CCHMASK 0x3 #define UAC2_DEF_CSRATE 64000 #define UAC2_DEF_CSSIZE 2 +#define UAC2_DEF_CSYNC USB_ENDPOINT_SYNC_ASYNC #define UAC2_DEF_REQ_NUM 2 struct f_uac2_opts { @@ -31,6 +32,7 @@ struct f_uac2_opts { int c_chmask; int c_srate; int c_ssize; + int c_sync; int req_number; bool bound; From patchwork Sun Nov 8 00:18:31 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ruslan Bilovol X-Patchwork-Id: 11889355 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id D6472139F for ; Sun, 8 Nov 2020 00:18:41 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id B42F420B1F for ; Sun, 8 Nov 2020 00:18:41 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="MrEvtgBz" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726493AbgKHASl (ORCPT ); Sat, 7 Nov 2020 19:18:41 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47928 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726043AbgKHASk (ORCPT ); Sat, 7 Nov 2020 19:18:40 -0500 Received: from mail-lj1-x243.google.com (mail-lj1-x243.google.com [IPv6:2a00:1450:4864:20::243]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 67B40C0613CF for ; Sat, 7 Nov 2020 16:18:40 -0800 (PST) Received: by mail-lj1-x243.google.com with SMTP id l10so5672046lji.4 for ; Sat, 07 Nov 2020 16:18:40 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=veQp2P7DrYXvwVV7d9MfC5kCF4EEdF+TkL5G1Cl/t7Y=; b=MrEvtgBzQOCHQ/tHMCdz5nq+IN3G+FxXz9KURcYRwolP6wnBl/WZJ1pIU0MduaRlAs gkYDpET7Y6mSRvnoMeT2uPvVb2sYTB4ZrY2vXxAepWxtyeKy13Ph97zkO/aQTyRCSsGE ikumib6BzV5QTPZEKKkcY5LY8HCtsk0hdepILsb+EysVS9W0E+WmiLQtdxXRh96gih22 THBvdVoxxQU6zFGoAktx7gFZ67e5E8Axf4NgbNa0ZpNa+Gih1W0SzKeJLR3Q7OdWrm/O Ct3JmHi/3DWiaQLd+9JTl+7dc6vkrTn6D4nd/38oXsREZ8iz13XWNe1bs87PahKsK+qG zYxw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=veQp2P7DrYXvwVV7d9MfC5kCF4EEdF+TkL5G1Cl/t7Y=; b=IOvl2/jTZqKTAXeA+XVAFl9jGwm6WN0QufxIoQFNLNtsiHvDbCM4WV32tK2Pi7ZS8V A5rQIqEGYvSrFfVy4ctW8CgpvtVJx4MilYFsC0V6/QTzby27SDiwyllySw2fAvIluyu1 Wox9k2FyfX04Q4gO/zP+2vLV5JKP1jKG/VyDJikgT99/+bCzyTgNkVcHxeMVkgvZ5u75 JwUV9jTzwL2jx0Kd0azabEowWxD7dIzrcHQJHKayZ0dS6kVR5eiif+aEaVoE9KNjZjw6 eQUiBGAbWYUA26DuF3sjwbH/vpqC3czUxXkF1+Jl+3xCEsONe2tEEQrLq2Our4BKCKK8 lq5g== X-Gm-Message-State: AOAM531IuqbGzXP8ei9e9p/KfH4KK0pHGj/h+VmuG028lKPZduJHXimi U7jpFpVLw/PUiYhw8qlEdpzmnmN+010= X-Google-Smtp-Source: ABdhPJxiph41GA4gYCPYYupNc/EE5imoMLYh8WLvWcauYsKfDzZQeDBF5GPq76U/dNCzdMREzH+C1w== X-Received: by 2002:a2e:93c9:: with SMTP id p9mr3579429ljh.444.1604794718798; Sat, 07 Nov 2020 16:18:38 -0800 (PST) Received: from localhost ([80.64.86.40]) by smtp.gmail.com with ESMTPSA id 27sm809713lfy.195.2020.11.07.16.18.37 (version=TLS1_2 cipher=AES128-SHA bits=128/128); Sat, 07 Nov 2020 16:18:38 -0800 (PST) From: Ruslan Bilovol To: balbi@kernel.org Cc: linux-usb@vger.kernel.org, gschmottlach@gmail.com Subject: [PATCH 3/3] usb: gadget: u_audio: add real feedback implementation Date: Sun, 8 Nov 2020 02:18:31 +0200 Message-Id: <1604794711-8661-4-git-send-email-ruslan.bilovol@gmail.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1604794711-8661-1-git-send-email-ruslan.bilovol@gmail.com> References: <1604794711-8661-1-git-send-email-ruslan.bilovol@gmail.com> Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org This adds interface between userspace and feedback endpoint to report real feedback frequency to the Host. Current implementation adds new userspace interface ALSA mixer control "PCM Feedback Frequency Hz" (similar to aloop driver's "PCM Rate Shift 100000" mixer control) We allow +/-20% deviation of nominal sampling frequency, that usually is more than enough in real-world usecases Usage of this new control is easy to implement in existing userspace tools like alsaloop from alsa-utils. Signed-off-by: Ruslan Bilovol --- drivers/usb/gadget/function/f_uac2.c | 4 ++ drivers/usb/gadget/function/u_audio.c | 93 +++++++++++++++++++++++++++++++++++ drivers/usb/gadget/function/u_audio.h | 7 +++ 3 files changed, 104 insertions(+) diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index 3187ad3..ebdb2a1 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -487,6 +487,10 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, max_packet_size = num_channels(chmask) * ssize * DIV_ROUND_UP(srate, factor / (1 << (ep_desc->bInterval - 1))); + + if (!is_playback && (uac2_opts->c_sync == USB_ENDPOINT_SYNC_ASYNC)) + max_packet_size = max_packet_size * FBACK_FREQ_MAX / 100; + ep_desc->wMaxPacketSize = cpu_to_le16(min_t(u16, max_packet_size, le16_to_cpu(ep_desc->wMaxPacketSize))); } diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c index 33c9288..ae9e1f3 100644 --- a/drivers/usb/gadget/function/u_audio.c +++ b/drivers/usb/gadget/function/u_audio.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "u_audio.h" @@ -596,12 +597,87 @@ void u_audio_stop_playback(struct g_audio *audio_dev) } EXPORT_SYMBOL_GPL(u_audio_stop_playback); +static int u_audio_rate_shift_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev = uac->audio_dev; + struct uac_params *params = &audio_dev->params; + unsigned int ffback_min, ffback_max; + + ffback_min = params->c_srate * FBACK_FREQ_MIN / 100; + ffback_max = params->c_srate * FBACK_FREQ_MIN / 100; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = ffback_min; + uinfo->value.integer.max = ffback_max; + uinfo->value.integer.step = 1; + return 0; +} + +static int u_audio_rate_shift_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&prm->lock, flags); + ucontrol->value.integer.value[0] = prm->ffback; + spin_unlock_irqrestore(&prm->lock, flags); + + return 0; +} + +static int u_audio_rate_shift_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev = uac->audio_dev; + struct uac_params *params = &audio_dev->params; + unsigned int val; + unsigned int ffback_min, ffback_max; + unsigned long flags; + int change = 0; + + ffback_min = params->c_srate * FBACK_FREQ_MIN / 100; + ffback_max = params->c_srate * FBACK_FREQ_MAX / 100; + + val = ucontrol->value.integer.value[0]; + if (val < ffback_min) + val = ffback_min; + if (val > ffback_max) + val = ffback_max; + + spin_lock_irqsave(&prm->lock, flags); + if (prm->ffback != val) { + prm->ffback = val; + change = 1; + } + spin_unlock_irqrestore(&prm->lock, flags); + + return change; +} + +static const struct snd_kcontrol_new u_audio_controls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Feedback Frequency Hz", + .info = u_audio_rate_shift_info, + .get = u_audio_rate_shift_get, + .put = u_audio_rate_shift_put, +}, +}; + int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, const char *card_name) { struct snd_uac_chip *uac; struct snd_card *card; struct snd_pcm *pcm; + struct snd_kcontrol *kctl; struct uac_params *params; int p_chmask, c_chmask; int err; @@ -687,6 +763,23 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac_pcm_ops); snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac_pcm_ops); + if (c_chmask && g_audio->in_ep_fback) { + strlcpy(card->mixername, card_name, sizeof(card->driver)); + + kctl = snd_ctl_new1(&u_audio_controls[0], &uac->c_prm); + if (!kctl) { + err = -ENOMEM; + goto snd_fail; + } + + kctl->id.device = pcm->device; + kctl->id.subdevice = 0; + + err = snd_ctl_add(card, kctl); + if (err < 0) + goto snd_fail; + } + strlcpy(card->driver, card_name, sizeof(card->driver)); strlcpy(card->shortname, card_name, sizeof(card->shortname)); sprintf(card->longname, "%s %i", card_name, card->dev->id); diff --git a/drivers/usb/gadget/function/u_audio.h b/drivers/usb/gadget/function/u_audio.h index 53e6baf..fd70808 100644 --- a/drivers/usb/gadget/function/u_audio.h +++ b/drivers/usb/gadget/function/u_audio.h @@ -11,6 +11,13 @@ #include +/* + * Min/max percentage of nominal sampling frequency deviation + * reported through feedback endpoint to the host + */ +#define FBACK_FREQ_MIN 80 +#define FBACK_FREQ_MAX 120 + struct uac_params { /* playback */ int p_chmask; /* channel mask */