[RFC] Add duplex sound support for USB devices using implicit feedback

Erwin Burema e.burema at freedom.nl
Sun May 10 20:30:43 CEST 2020


On zondag 10 mei 2020 19:31:51 CEST Takashi Iwai wrote:
> On Sat, 09 May 2020 20:10:47 +0200,
> 
> Erwin Burema wrote:
> > Hi,
> > 
> > Since I had problems getting duplex audio to work on my motu m2 I created
> > the following patch to get duplex audio to work. This is based on my
> > understanding that when implicit feedback is used the size of the next
> > output is determined by the previous input, for this the content of the
> > input is ignored but in case of duplex audio the content can actually
> > contain audio information, so long as both input and output are
> > configured the same both sould be usable.
> > 
> > With the above in mind an endpoint can be opened/configured a second time
> > so long as it is an implicit sync endpoint and the config does not
> > change. So that is what this patch does, the check if the config does not
> > change might be a bit more complex then needed, but it seems to work on
> > my motu m2.
> > 
> > Fixes bug 207023 and should fix 103751 as well
> 
> The code changes look good and even if it's not perfect, it can't be
> worse than the current situation, so I'd happily take it.
> 
> But could you submit in a formal way, especially with your
> Signed-off-by line, and with the proper patch description?
> 
> 

Send as patch with signed-off-by line and hopefully a proper patch description, 
if anything is wrong please let me know (I think this is my first patch to the 
kernel to be honest)

Kind Regards

Erwin Burema

> thanks,
> 
> Takashi
> 
> > Kind Regards,
> > 
> > Erwin Burema
> > 
> > ---
> > 
> >  sound/usb/card.h     |   1 +
> >  sound/usb/endpoint.c | 195 ++++++++++++++++++++++++++++++++++++++++++-
> >  sound/usb/pcm.c      |   5 ++
> >  3 files changed, 197 insertions(+), 4 deletions(-)
> > 
> > diff --git a/sound/usb/card.h b/sound/usb/card.h
> > index 820e564656ed..d6219fba9699 100644
> > --- a/sound/usb/card.h
> > +++ b/sound/usb/card.h
> > @@ -108,6 +108,7 @@ struct snd_usb_endpoint {
> > 
> >  	int iface, altsetting;
> >  	int skip_packets;		/* quirks for devices to 
ignore the first n packets
> >  	
> >  					   in a stream */
> > 
> > +	bool is_implicit_feedback;      /* This endpoint is used as 
implicit
> > feedback */> 
> >  	spinlock_t lock;
> >  	struct list_head list;
> > 
> > diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c
> > index 50104f658ed4..9bea7d3f99f8 100644
> > --- a/sound/usb/endpoint.c
> > +++ b/sound/usb/endpoint.c
> > @@ -522,6 +522,8 @@ struct snd_usb_endpoint *snd_usb_add_endpoint(struct
> > snd_usb_audio *chip,> 
> >  	list_add_tail(&ep->list, &chip->ep_list);
> > 
> > +	ep->is_implicit_feedback = 0;
> > +
> > 
> >  __exit_unlock:
> >  	mutex_unlock(&chip->mutex);
> > 
> > @@ -621,6 +623,178 @@ static void release_urbs(struct snd_usb_endpoint
> > *ep, int force)> 
> >  	ep->nurbs = 0;
> >  
> >  }
> > 
> > +/*
> > + * Check data endpoint for format differences
> > + */
> > +static bool check_ep_params(struct snd_usb_endpoint *ep,
> > +			      snd_pcm_format_t pcm_format,
> > +			      unsigned int channels,
> > +			      unsigned int period_bytes,
> > +			      unsigned int frames_per_period,
> > +			      unsigned int periods_per_buffer,
> > +			      struct audioformat *fmt,
> > +			      struct snd_usb_endpoint *sync_ep)
> > +{
> > +	unsigned int maxsize, minsize, packs_per_ms, max_packs_per_urb;
> > +	unsigned int max_packs_per_period, urbs_per_period, urb_packs;
> > +	unsigned int max_urbs;
> > +	int frame_bits = snd_pcm_format_physical_width(pcm_format) * 
channels;
> > +	int tx_length_quirk = (ep->chip->tx_length_quirk &&
> > +			       usb_pipeout(ep->pipe));
> > +	bool ret = 1;
> > +
> > +	if (pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE && fmt->dsd_dop) {
> > +		/*
> > +		 * When operating in DSD DOP mode, the size of a sample 
frame
> > +		 * in hardware differs from the actual physical format 
width
> > +		 * because we need to make room for the DOP markers.
> > +		 */
> > +		frame_bits += channels << 3;
> > +	}
> > +
> > +	ret = ret && (ep->datainterval == fmt->datainterval);
> > +	ret = ret && (ep->stride == frame_bits >> 3);
> > +
> > +	switch (pcm_format) {
> > +	case SNDRV_PCM_FORMAT_U8:
> > +		ret = ret && (ep->silence_value == 0x80);
> > +		break;
> > +	case SNDRV_PCM_FORMAT_DSD_U8:
> > +	case SNDRV_PCM_FORMAT_DSD_U16_LE:
> > +	case SNDRV_PCM_FORMAT_DSD_U32_LE:
> > +	case SNDRV_PCM_FORMAT_DSD_U16_BE:
> > +	case SNDRV_PCM_FORMAT_DSD_U32_BE:
> > +		ret = ret && (ep->silence_value == 0x69);
> > +		break;
> > +	default:
> > +		ret = ret && (ep->silence_value == 0);
> > +	}
> > +
> > +	/* assume max. frequency is 50% higher than nominal */
> > +	ret = ret && (ep->freqmax == ep->freqn + (ep->freqn >> 1));
> > +	/* Round up freqmax to nearest integer in order to calculate 
maximum
> > +	 * packet size, which must represent a whole number of frames.
> > +	 * This is accomplished by adding 0x0.ffff before converting the
> > +	 * Q16.16 format into integer.
> > +	 * In order to accurately calculate the maximum packet size when
> > +	 * the data interval is more than 1 (i.e. ep->datainterval > 0),
> > +	 * multiply by the data interval prior to rounding. For instance,
> > +	 * a freqmax of 41 kHz will result in a max packet size of 6 
(5.125)
> > +	 * frames with a data interval of 1, but 11 (10.25) frames with a
> > +	 * data interval of 2.
> > +	 * (ep->freqmax << ep->datainterval overflows at 8.192 MHz for the
> > +	 * maximum datainterval value of 3, at USB full speed, higher for
> > +	 * USB high speed, noting that ep->freqmax is in units of
> > +	 * frames per packet in Q16.16 format.)
> > +	 */
> > +	maxsize = (((ep->freqmax << ep->datainterval) + 0xffff) >> 16) *
> > +			 (frame_bits >> 3);
> > +	if (tx_length_quirk)
> > +		maxsize += sizeof(__le32); /* Space for length 
descriptor */
> > +	/* but wMaxPacketSize might reduce this */
> > +	if (ep->maxpacksize && ep->maxpacksize < maxsize) {
> > +		/* whatever fits into a max. size packet */
> > +		unsigned int data_maxsize = maxsize = ep->maxpacksize;
> > +
> > +		if (tx_length_quirk)
> > +			/* Need to remove the length descriptor to 
calc freq */
> > +			data_maxsize -= sizeof(__le32);
> > +		ret = ret && (ep->freqmax == (data_maxsize / 
(frame_bits >> 3))
> > +				<< (16 - ep->datainterval));
> > +	}
> > +
> > +	if (ep->fill_max)
> > +		ret = ret && (ep->curpacksize == ep->maxpacksize);
> > +	else
> > +		ret = ret && (ep->curpacksize == maxsize);
> > +
> > +	if (snd_usb_get_speed(ep->chip->dev) != USB_SPEED_FULL) {
> > +		packs_per_ms = 8 >> ep->datainterval;
> > +		max_packs_per_urb = MAX_PACKS_HS;
> > +	} else {
> > +		packs_per_ms = 1;
> > +		max_packs_per_urb = MAX_PACKS;
> > +	}
> > +	if (sync_ep && !snd_usb_endpoint_implicit_feedback_sink(ep))
> > +		max_packs_per_urb = min(max_packs_per_urb,
> > +					1U << sync_ep-
>syncinterval);
> > +	max_packs_per_urb = max(1u, max_packs_per_urb >> ep-
>datainterval);
> > +
> > +	/*
> > +	 * Capture endpoints need to use small URBs because there's no way
> > +	 * to tell in advance where the next period will end, and we don't
> > +	 * want the next URB to complete much after the period ends.
> > +	 *
> > +	 * Playback endpoints with implicit sync much use the same 
parameters
> > +	 * as their corresponding capture endpoint.
> > +	 */
> > +	if (usb_pipein(ep->pipe) ||
> > +			snd_usb_endpoint_implicit_feedback_sink(ep)) 
{
> > +
> > +		urb_packs = packs_per_ms;
> > +		/*
> > +		 * Wireless devices can poll at a max rate of once per 
4ms.
> > +		 * For dataintervals less than 5, increase the packet 
count to
> > +		 * allow the host controller to use bursting to fill in 
the
> > +		 * gaps.
> > +		 */
> > +		if (snd_usb_get_speed(ep->chip->dev) == 
USB_SPEED_WIRELESS) {
> > +			int interval = ep->datainterval;
> > +
> > +			while (interval < 5) {
> > +				urb_packs <<= 1;
> > +				++interval;
> > +			}
> > +		}
> > +		/* make capture URBs <= 1 ms and smaller than a period 
*/
> > +		urb_packs = min(max_packs_per_urb, urb_packs);
> > +		while (urb_packs > 1 && urb_packs * maxsize >= 
period_bytes)
> > +			urb_packs >>= 1;
> > +		ret = ret && (ep->nurbs == MAX_URBS);
> > +
> > +	/*
> > +	 * Playback endpoints without implicit sync are adjusted so that
> > +	 * a period fits as evenly as possible in the smallest number of
> > +	 * URBs.  The total number of URBs is adjusted to the size of the
> > +	 * ALSA buffer, subject to the MAX_URBS and MAX_QUEUE limits.
> > +	 */
> > +	} else {
> > +		/* determine how small a packet can be */
> > +		minsize = (ep->freqn >> (16 - ep->datainterval)) *
> > +				(frame_bits >> 3);
> > +		/* with sync from device, assume it can be 12% lower */
> > +		if (sync_ep)
> > +			minsize -= minsize >> 3;
> > +		minsize = max(minsize, 1u);
> > +
> > +		/* how many packets will contain an entire ALSA period? 
*/
> > +		max_packs_per_period = DIV_ROUND_UP(period_bytes, 
minsize);
> > +
> > +		/* how many URBs will contain a period? */
> > +		urbs_per_period = DIV_ROUND_UP(max_packs_per_period,
> > +				max_packs_per_urb);
> > +		/* how many packets are needed in each URB? */
> > +		urb_packs = DIV_ROUND_UP(max_packs_per_period, 
urbs_per_period);
> > +
> > +		/* limit the number of frames in a single URB */
> > +		ret = ret && (ep->max_urb_frames ==
> > +			DIV_ROUND_UP(frames_per_period, 
urbs_per_period));
> > +
> > +		/* try to use enough URBs to contain an entire ALSA 
buffer */
> > +		max_urbs = min((unsigned) MAX_URBS,
> > +				MAX_QUEUE * packs_per_ms / 
urb_packs);
> > +		ret = ret && (ep->nurbs == min(max_urbs,
> > +				urbs_per_period * 
periods_per_buffer));
> > +	}
> > +
> > +	ret = ret && (ep->datainterval == fmt->datainterval);
> > +	ret = ret && (ep->maxpacksize == fmt->maxpacksize);
> > +	ret = ret &&
> > +		(ep->fill_max == !!(fmt->attributes & 
UAC_EP_CS_ATTR_FILL_MAX));
> > +
> > +	return ret;
> > +}
> > +
> > 
> >  /*
> >  
> >   * configure a data endpoint
> >   */
> > 
> > @@ -886,10 +1060,23 @@ int snd_usb_endpoint_set_params(struct
> > snd_usb_endpoint *ep,> 
> >  	int err;
> >  	
> >  	if (ep->use_count != 0) {
> > 
> > -		usb_audio_warn(ep->chip,
> > -			 "Unable to change format on ep #%x: already 
in use\n",
> > -			 ep->ep_num);
> > -		return -EBUSY;
> > +		bool check = ep->is_implicit_feedback &&
> > +			check_ep_params(ep, pcm_format,
> > +					     channels, 
period_bytes,
> > +					     period_frames, 
buffer_periods,
> > +					     fmt, sync_ep);
> > +
> > +		if (!check) {
> > +			usb_audio_warn(ep->chip,
> > +				"Unable to change format on ep 
#%x: already in use\n",
> > +				ep->ep_num);
> > +			return -EBUSY;
> > +		}
> > +
> > +		usb_audio_dbg(ep->chip,
> > +			      "Ep #%x already in use as implicit 
feedback but format not
> > changed\n", +			      ep->ep_num);
> > +		return 0;
> > 
> >  	}
> >  	
> >  	/* release old buffers, if any */
> > 
> > diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c
> > index b50965ab3b3a..d61c2f1095b5 100644
> > --- a/sound/usb/pcm.c
> > +++ b/sound/usb/pcm.c
> > @@ -404,6 +404,8 @@ static int set_sync_ep_implicit_fb_quirk(struct
> > snd_usb_substream *subs,> 
> >  	if (!subs->sync_endpoint)
> >  	
> >  		return -EINVAL;
> > 
> > +	subs->sync_endpoint->is_implicit_feedback = 1;
> > +
> > 
> >  	subs->data_endpoint->sync_master = subs->sync_endpoint;
> >  	
> >  	return 1;
> > 
> > @@ -502,12 +504,15 @@ static int set_sync_endpoint(struct
> > snd_usb_substream *subs,> 
> >  						   
implicit_fb ?
> >  							
> >  							
SND_USB_ENDPOINT_TYPE_DATA :
> >  							
SND_USB_ENDPOINT_TYPE_SYNC);
> > 
> > +
> > 
> >  	if (!subs->sync_endpoint) {
> >  	
> >  		if (is_playback && attr == USB_ENDPOINT_SYNC_NONE)
> >  		
> >  			return 0;
> >  		
> >  		return -EINVAL;
> >  	
> >  	}
> > 
> > +	subs->sync_endpoint->is_implicit_feedback = implicit_fb;
> > +
> > 
> >  	subs->data_endpoint->sync_master = subs->sync_endpoint;
> >  	
> >  	return 0;






More information about the Alsa-devel mailing list