Commit 254cb1e0 authored by Ruslan Bilovol's avatar Ruslan Bilovol Committed by Greg Kroah-Hartman
Browse files

usb: gadget: f_uac1: disable IN/OUT ep if unused



User can configure f_uac1 function via p_chmask/c_chmask
whether uac1 shall support playback and/or capture,
but it has only effect on the created ALSA device,
but not on the USB descriptor.

This patch adds playback/capture descriptors
dependent on that parameter. It is similar to
the same conversion done earlier for f_uac2

Signed-off-by: default avatarRuslan Bilovol <ruslan.bilovol@gmail.com>
Link: https://lore.kernel.org/r/1614599375-8803-6-git-send-email-ruslan.bilovol@gmail.com


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent a59c68a6
Loading
Loading
Loading
Loading
+163 −66
Original line number Diff line number Diff line
@@ -22,6 +22,9 @@
/* UAC1 spec: 3.7.2.3 Audio Channel Cluster Format */
#define UAC1_CHANNEL_MASK 0x0FFF

#define EPIN_EN(_opts) ((_opts)->p_chmask != 0)
#define EPOUT_EN(_opts) ((_opts)->c_chmask != 0)

struct f_uac1 {
	struct g_audio g_audio;
	u8 ac_intf, as_in_intf, as_out_intf;
@@ -50,11 +53,6 @@ static inline struct f_uac1_opts *g_audio_to_uac1_opts(struct g_audio *audio)
 * USB-OUT -> IT_1 -> OT_2 -> ALSA_Capture
 * ALSA_Playback -> IT_3 -> OT_4 -> USB-IN
 */
#define F_AUDIO_AC_INTERFACE		0
#define F_AUDIO_AS_OUT_INTERFACE	1
#define F_AUDIO_AS_IN_INTERFACE		2
/* Number of streaming interfaces */
#define F_AUDIO_NUM_INTERFACES		2

/* B.3.1  Standard AC Interface Descriptor */
static struct usb_interface_descriptor ac_interface_desc = {
@@ -65,73 +63,47 @@ static struct usb_interface_descriptor ac_interface_desc = {
	.bInterfaceSubClass =	USB_SUBCLASS_AUDIOCONTROL,
};

/*
 * The number of AudioStreaming and MIDIStreaming interfaces
 * in the Audio Interface Collection
 */
DECLARE_UAC_AC_HEADER_DESCRIPTOR(2);

#define UAC_DT_AC_HEADER_LENGTH	UAC_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES)
/* 2 input terminals and 2 output terminals */
#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH \
	+ 2*UAC_DT_INPUT_TERMINAL_SIZE + 2*UAC_DT_OUTPUT_TERMINAL_SIZE)
/* B.3.2  Class-Specific AC Interface Descriptor */
static struct uac1_ac_header_descriptor_2 ac_header_desc = {
	.bLength =		UAC_DT_AC_HEADER_LENGTH,
	.bDescriptorType =	USB_DT_CS_INTERFACE,
	.bDescriptorSubtype =	UAC_HEADER,
	.bcdADC =		cpu_to_le16(0x0100),
	.wTotalLength =		cpu_to_le16(UAC_DT_TOTAL_LENGTH),
	.bInCollection =	F_AUDIO_NUM_INTERFACES,
	.baInterfaceNr = {
	/* Interface number of the AudioStream interfaces */
		[0] =		1,
		[1] =		2,
	}
};
static struct uac1_ac_header_descriptor *ac_header_desc;

#define USB_OUT_IT_ID	1
static struct uac_input_terminal_descriptor usb_out_it_desc = {
	.bLength =		UAC_DT_INPUT_TERMINAL_SIZE,
	.bDescriptorType =	USB_DT_CS_INTERFACE,
	.bDescriptorSubtype =	UAC_INPUT_TERMINAL,
	.bTerminalID =		USB_OUT_IT_ID,
	/* .bTerminalID =	DYNAMIC */
	.wTerminalType =	cpu_to_le16(UAC_TERMINAL_STREAMING),
	.bAssocTerminal =	0,
	.wChannelConfig =	cpu_to_le16(0x3),
};

#define IO_OUT_OT_ID	2
static struct uac1_output_terminal_descriptor io_out_ot_desc = {
	.bLength		= UAC_DT_OUTPUT_TERMINAL_SIZE,
	.bDescriptorType	= USB_DT_CS_INTERFACE,
	.bDescriptorSubtype	= UAC_OUTPUT_TERMINAL,
	.bTerminalID		= IO_OUT_OT_ID,
	/* .bTerminalID =	DYNAMIC */
	.wTerminalType		= cpu_to_le16(UAC_OUTPUT_TERMINAL_SPEAKER),
	.bAssocTerminal		= 0,
	.bSourceID		= USB_OUT_IT_ID,
	/* .bSourceID =		DYNAMIC */
};

#define IO_IN_IT_ID	3
static struct uac_input_terminal_descriptor io_in_it_desc = {
	.bLength		= UAC_DT_INPUT_TERMINAL_SIZE,
	.bDescriptorType	= USB_DT_CS_INTERFACE,
	.bDescriptorSubtype	= UAC_INPUT_TERMINAL,
	.bTerminalID		= IO_IN_IT_ID,
	/* .bTerminalID		= DYNAMIC */
	.wTerminalType		= cpu_to_le16(UAC_INPUT_TERMINAL_MICROPHONE),
	.bAssocTerminal		= 0,
	.wChannelConfig		= cpu_to_le16(0x3),
};

#define USB_IN_OT_ID	4
static struct uac1_output_terminal_descriptor usb_in_ot_desc = {
	.bLength =		UAC_DT_OUTPUT_TERMINAL_SIZE,
	.bDescriptorType =	USB_DT_CS_INTERFACE,
	.bDescriptorSubtype =	UAC_OUTPUT_TERMINAL,
	.bTerminalID =		USB_IN_OT_ID,
	/* .bTerminalID =	DYNAMIC */
	.wTerminalType =	cpu_to_le16(UAC_TERMINAL_STREAMING),
	.bAssocTerminal =	0,
	.bSourceID =		IO_IN_IT_ID,
	/* .bSourceID =		DYNAMIC */
};

/* B.4.1  Standard AS Interface Descriptor */
@@ -176,7 +148,7 @@ static struct uac1_as_header_descriptor as_out_header_desc = {
	.bLength =		UAC_DT_AS_HEADER_SIZE,
	.bDescriptorType =	USB_DT_CS_INTERFACE,
	.bDescriptorSubtype =	UAC_AS_GENERAL,
	.bTerminalLink =	USB_OUT_IT_ID,
	/* .bTerminalLink =	DYNAMIC */
	.bDelay =		1,
	.wFormatTag =		cpu_to_le16(UAC_FORMAT_TYPE_I_PCM),
};
@@ -185,7 +157,7 @@ static struct uac1_as_header_descriptor as_in_header_desc = {
	.bLength =		UAC_DT_AS_HEADER_SIZE,
	.bDescriptorType =	USB_DT_CS_INTERFACE,
	.bDescriptorSubtype =	UAC_AS_GENERAL,
	.bTerminalLink =	USB_IN_OT_ID,
	/* .bTerminalLink =	DYNAMIC */
	.bDelay =		1,
	.wFormatTag =		cpu_to_le16(UAC_FORMAT_TYPE_I_PCM),
};
@@ -513,6 +485,108 @@ static void f_audio_disable(struct usb_function *f)

/*-------------------------------------------------------------------------*/

static struct
uac1_ac_header_descriptor *build_ac_header_desc(struct f_uac1_opts *opts)
{
	struct uac1_ac_header_descriptor *ac_desc;
	int ac_header_desc_size;
	int num_ifaces = 0;

	if (EPOUT_EN(opts))
		num_ifaces++;
	if (EPIN_EN(opts))
		num_ifaces++;

	ac_header_desc_size = UAC_DT_AC_HEADER_SIZE(num_ifaces);

	ac_desc = kzalloc(ac_header_desc_size, GFP_KERNEL);
	if (!ac_desc)
		return NULL;

	ac_desc->bLength = ac_header_desc_size;
	ac_desc->bDescriptorType = USB_DT_CS_INTERFACE;
	ac_desc->bDescriptorSubtype = UAC_HEADER;
	ac_desc->bcdADC = cpu_to_le16(0x0100);
	ac_desc->bInCollection = num_ifaces;

	/* wTotalLength and baInterfaceNr will be defined later */

	return ac_desc;
}

/* Use macro to overcome line length limitation */
#define USBDHDR(p) (struct usb_descriptor_header *)(p)

static void setup_descriptor(struct f_uac1_opts *opts)
{
	/* patch descriptors */
	int i = 1; /* ID's start with 1 */

	if (EPOUT_EN(opts))
		usb_out_it_desc.bTerminalID = i++;
	if (EPIN_EN(opts))
		io_in_it_desc.bTerminalID = i++;
	if (EPOUT_EN(opts))
		io_out_ot_desc.bTerminalID = i++;
	if (EPIN_EN(opts))
		usb_in_ot_desc.bTerminalID = i++;

	usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID;
	io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID;

	as_out_header_desc.bTerminalLink = usb_out_it_desc.bTerminalID;
	as_in_header_desc.bTerminalLink = usb_in_ot_desc.bTerminalID;

	ac_header_desc->wTotalLength = cpu_to_le16(ac_header_desc->bLength);

	if (EPIN_EN(opts)) {
		u16 len = le16_to_cpu(ac_header_desc->wTotalLength);

		len += sizeof(usb_in_ot_desc);
		len += sizeof(io_in_it_desc);
		ac_header_desc->wTotalLength = cpu_to_le16(len);
	}
	if (EPOUT_EN(opts)) {
		u16 len = le16_to_cpu(ac_header_desc->wTotalLength);

		len += sizeof(usb_out_it_desc);
		len += sizeof(io_out_ot_desc);
		ac_header_desc->wTotalLength = cpu_to_le16(len);
	}

	i = 0;
	f_audio_desc[i++] = USBDHDR(&ac_interface_desc);
	f_audio_desc[i++] = USBDHDR(ac_header_desc);

	if (EPOUT_EN(opts)) {
		f_audio_desc[i++] = USBDHDR(&usb_out_it_desc);
		f_audio_desc[i++] = USBDHDR(&io_out_ot_desc);
	}

	if (EPIN_EN(opts)) {
		f_audio_desc[i++] = USBDHDR(&io_in_it_desc);
		f_audio_desc[i++] = USBDHDR(&usb_in_ot_desc);
	}

	if (EPOUT_EN(opts)) {
		f_audio_desc[i++] = USBDHDR(&as_out_interface_alt_0_desc);
		f_audio_desc[i++] = USBDHDR(&as_out_interface_alt_1_desc);
		f_audio_desc[i++] = USBDHDR(&as_out_header_desc);
		f_audio_desc[i++] = USBDHDR(&as_out_type_i_desc);
		f_audio_desc[i++] = USBDHDR(&as_out_ep_desc);
		f_audio_desc[i++] = USBDHDR(&as_iso_out_desc);
	}
	if (EPIN_EN(opts)) {
		f_audio_desc[i++] = USBDHDR(&as_in_interface_alt_0_desc);
		f_audio_desc[i++] = USBDHDR(&as_in_interface_alt_1_desc);
		f_audio_desc[i++] = USBDHDR(&as_in_header_desc);
		f_audio_desc[i++] = USBDHDR(&as_in_type_i_desc);
		f_audio_desc[i++] = USBDHDR(&as_in_ep_desc);
		f_audio_desc[i++] = USBDHDR(&as_iso_in_desc);
	}
	f_audio_desc[i] = NULL;
}

static int f_audio_validate_opts(struct g_audio *audio, struct device *dev)
{
	struct f_uac1_opts *opts = g_audio_to_uac1_opts(audio);
@@ -556,6 +630,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
	struct usb_string		*us;
	u8				*sam_freq;
	int				rate;
	int				ba_iface_id;
	int				status;

	status = f_audio_validate_opts(audio, dev);
@@ -567,6 +642,11 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
	us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1));
	if (IS_ERR(us))
		return PTR_ERR(us);

	ac_header_desc = build_ac_header_desc(audio_opts);
	if (!ac_header_desc)
		return -ENOMEM;

	ac_interface_desc.iInterface = us[STR_AC_IF].id;
	usb_out_it_desc.iTerminal = us[STR_USB_OUT_IT].id;
	usb_out_it_desc.iChannelNames = us[STR_USB_OUT_IT_CH_NAMES].id;
@@ -607,40 +687,52 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
	uac1->ac_intf = status;
	uac1->ac_alt = 0;

	ba_iface_id = 0;

	if (EPOUT_EN(audio_opts)) {
		status = usb_interface_id(c, f);
		if (status < 0)
			goto fail;
		as_out_interface_alt_0_desc.bInterfaceNumber = status;
		as_out_interface_alt_1_desc.bInterfaceNumber = status;
	ac_header_desc.baInterfaceNr[0] = status;
		ac_header_desc->baInterfaceNr[ba_iface_id++] = status;
		uac1->as_out_intf = status;
		uac1->as_out_alt = 0;
	}

	if (EPIN_EN(audio_opts)) {
		status = usb_interface_id(c, f);
		if (status < 0)
			goto fail;
		as_in_interface_alt_0_desc.bInterfaceNumber = status;
		as_in_interface_alt_1_desc.bInterfaceNumber = status;
	ac_header_desc.baInterfaceNr[1] = status;
		ac_header_desc->baInterfaceNr[ba_iface_id++] = status;
		uac1->as_in_intf = status;
		uac1->as_in_alt = 0;
	}

	audio->gadget = gadget;

	status = -ENODEV;

	/* allocate instance-specific endpoints */
	if (EPOUT_EN(audio_opts)) {
		ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc);
		if (!ep)
			goto fail;
		audio->out_ep = ep;
		audio->out_ep->desc = &as_out_ep_desc;
	}

	if (EPIN_EN(audio_opts)) {
		ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc);
		if (!ep)
			goto fail;
		audio->in_ep = ep;
		audio->in_ep->desc = &as_in_ep_desc;
	}

	setup_descriptor(audio_opts);

	/* copy descriptors, and track endpoint copies */
	status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL,
@@ -667,6 +759,8 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
err_card_register:
	usb_free_all_descriptors(f);
fail:
	kfree(ac_header_desc);
	ac_header_desc = NULL;
	return status;
}

@@ -809,6 +903,9 @@ static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f)
	g_audio_cleanup(audio);
	usb_free_all_descriptors(f);

	kfree(ac_header_desc);
	ac_header_desc = NULL;

	audio->gadget = NULL;
}