162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * f_obex.c -- USB CDC OBEX function driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2008 Nokia Corporation
662306a36Sopenharmony_ci * Contact: Felipe Balbi <felipe.balbi@nokia.com>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Based on f_acm.c by Al Borchers and David Brownell.
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci/* #define VERBOSE_DEBUG */
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci#include <linux/kernel.h>
1562306a36Sopenharmony_ci#include <linux/device.h>
1662306a36Sopenharmony_ci#include <linux/module.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include "u_serial.h"
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/*
2262306a36Sopenharmony_ci * This CDC OBEX function support just packages a TTY-ish byte stream.
2362306a36Sopenharmony_ci * A user mode server will put it into "raw" mode and handle all the
2462306a36Sopenharmony_ci * relevant protocol details ... this is just a kernel passthrough.
2562306a36Sopenharmony_ci * When possible, we prevent gadget enumeration until that server is
2662306a36Sopenharmony_ci * ready to handle the commands.
2762306a36Sopenharmony_ci */
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistruct f_obex {
3062306a36Sopenharmony_ci	struct gserial			port;
3162306a36Sopenharmony_ci	u8				ctrl_id;
3262306a36Sopenharmony_ci	u8				data_id;
3362306a36Sopenharmony_ci	u8				cur_alt;
3462306a36Sopenharmony_ci	u8				port_num;
3562306a36Sopenharmony_ci};
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistatic inline struct f_obex *func_to_obex(struct usb_function *f)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	return container_of(f, struct f_obex, port.func);
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic inline struct f_obex *port_to_obex(struct gserial *p)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	return container_of(p, struct f_obex, port);
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci/*-------------------------------------------------------------------------*/
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci#define OBEX_CTRL_IDX	0
5062306a36Sopenharmony_ci#define OBEX_DATA_IDX	1
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic struct usb_string obex_string_defs[] = {
5362306a36Sopenharmony_ci	[OBEX_CTRL_IDX].s	= "CDC Object Exchange (OBEX)",
5462306a36Sopenharmony_ci	[OBEX_DATA_IDX].s	= "CDC OBEX Data",
5562306a36Sopenharmony_ci	{  },	/* end of list */
5662306a36Sopenharmony_ci};
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic struct usb_gadget_strings obex_string_table = {
5962306a36Sopenharmony_ci	.language		= 0x0409,	/* en-US */
6062306a36Sopenharmony_ci	.strings		= obex_string_defs,
6162306a36Sopenharmony_ci};
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_cistatic struct usb_gadget_strings *obex_strings[] = {
6462306a36Sopenharmony_ci	&obex_string_table,
6562306a36Sopenharmony_ci	NULL,
6662306a36Sopenharmony_ci};
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci/*-------------------------------------------------------------------------*/
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic struct usb_interface_descriptor obex_control_intf = {
7162306a36Sopenharmony_ci	.bLength		= sizeof(obex_control_intf),
7262306a36Sopenharmony_ci	.bDescriptorType	= USB_DT_INTERFACE,
7362306a36Sopenharmony_ci	.bInterfaceNumber	= 0,
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	.bAlternateSetting	= 0,
7662306a36Sopenharmony_ci	.bNumEndpoints		= 0,
7762306a36Sopenharmony_ci	.bInterfaceClass	= USB_CLASS_COMM,
7862306a36Sopenharmony_ci	.bInterfaceSubClass	= USB_CDC_SUBCLASS_OBEX,
7962306a36Sopenharmony_ci};
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_cistatic struct usb_interface_descriptor obex_data_nop_intf = {
8262306a36Sopenharmony_ci	.bLength		= sizeof(obex_data_nop_intf),
8362306a36Sopenharmony_ci	.bDescriptorType	= USB_DT_INTERFACE,
8462306a36Sopenharmony_ci	.bInterfaceNumber	= 1,
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	.bAlternateSetting	= 0,
8762306a36Sopenharmony_ci	.bNumEndpoints		= 0,
8862306a36Sopenharmony_ci	.bInterfaceClass	= USB_CLASS_CDC_DATA,
8962306a36Sopenharmony_ci};
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_cistatic struct usb_interface_descriptor obex_data_intf = {
9262306a36Sopenharmony_ci	.bLength		= sizeof(obex_data_intf),
9362306a36Sopenharmony_ci	.bDescriptorType	= USB_DT_INTERFACE,
9462306a36Sopenharmony_ci	.bInterfaceNumber	= 2,
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	.bAlternateSetting	= 1,
9762306a36Sopenharmony_ci	.bNumEndpoints		= 2,
9862306a36Sopenharmony_ci	.bInterfaceClass	= USB_CLASS_CDC_DATA,
9962306a36Sopenharmony_ci};
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic struct usb_cdc_header_desc obex_cdc_header_desc = {
10262306a36Sopenharmony_ci	.bLength		= sizeof(obex_cdc_header_desc),
10362306a36Sopenharmony_ci	.bDescriptorType	= USB_DT_CS_INTERFACE,
10462306a36Sopenharmony_ci	.bDescriptorSubType	= USB_CDC_HEADER_TYPE,
10562306a36Sopenharmony_ci	.bcdCDC			= cpu_to_le16(0x0120),
10662306a36Sopenharmony_ci};
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistatic struct usb_cdc_union_desc obex_cdc_union_desc = {
10962306a36Sopenharmony_ci	.bLength		= sizeof(obex_cdc_union_desc),
11062306a36Sopenharmony_ci	.bDescriptorType	= USB_DT_CS_INTERFACE,
11162306a36Sopenharmony_ci	.bDescriptorSubType	= USB_CDC_UNION_TYPE,
11262306a36Sopenharmony_ci	.bMasterInterface0	= 1,
11362306a36Sopenharmony_ci	.bSlaveInterface0	= 2,
11462306a36Sopenharmony_ci};
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic struct usb_cdc_obex_desc obex_desc = {
11762306a36Sopenharmony_ci	.bLength		= sizeof(obex_desc),
11862306a36Sopenharmony_ci	.bDescriptorType	= USB_DT_CS_INTERFACE,
11962306a36Sopenharmony_ci	.bDescriptorSubType	= USB_CDC_OBEX_TYPE,
12062306a36Sopenharmony_ci	.bcdVersion		= cpu_to_le16(0x0100),
12162306a36Sopenharmony_ci};
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci/* High-Speed Support */
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cistatic struct usb_endpoint_descriptor obex_hs_ep_out_desc = {
12662306a36Sopenharmony_ci	.bLength		= USB_DT_ENDPOINT_SIZE,
12762306a36Sopenharmony_ci	.bDescriptorType	= USB_DT_ENDPOINT,
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	.bEndpointAddress	= USB_DIR_OUT,
13062306a36Sopenharmony_ci	.bmAttributes		= USB_ENDPOINT_XFER_BULK,
13162306a36Sopenharmony_ci	.wMaxPacketSize		= cpu_to_le16(512),
13262306a36Sopenharmony_ci};
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_cistatic struct usb_endpoint_descriptor obex_hs_ep_in_desc = {
13562306a36Sopenharmony_ci	.bLength		= USB_DT_ENDPOINT_SIZE,
13662306a36Sopenharmony_ci	.bDescriptorType	= USB_DT_ENDPOINT,
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	.bEndpointAddress	= USB_DIR_IN,
13962306a36Sopenharmony_ci	.bmAttributes		= USB_ENDPOINT_XFER_BULK,
14062306a36Sopenharmony_ci	.wMaxPacketSize		= cpu_to_le16(512),
14162306a36Sopenharmony_ci};
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_cistatic struct usb_descriptor_header *hs_function[] = {
14462306a36Sopenharmony_ci	(struct usb_descriptor_header *) &obex_control_intf,
14562306a36Sopenharmony_ci	(struct usb_descriptor_header *) &obex_cdc_header_desc,
14662306a36Sopenharmony_ci	(struct usb_descriptor_header *) &obex_desc,
14762306a36Sopenharmony_ci	(struct usb_descriptor_header *) &obex_cdc_union_desc,
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	(struct usb_descriptor_header *) &obex_data_nop_intf,
15062306a36Sopenharmony_ci	(struct usb_descriptor_header *) &obex_data_intf,
15162306a36Sopenharmony_ci	(struct usb_descriptor_header *) &obex_hs_ep_in_desc,
15262306a36Sopenharmony_ci	(struct usb_descriptor_header *) &obex_hs_ep_out_desc,
15362306a36Sopenharmony_ci	NULL,
15462306a36Sopenharmony_ci};
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci/* Full-Speed Support */
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_cistatic struct usb_endpoint_descriptor obex_fs_ep_in_desc = {
15962306a36Sopenharmony_ci	.bLength		= USB_DT_ENDPOINT_SIZE,
16062306a36Sopenharmony_ci	.bDescriptorType	= USB_DT_ENDPOINT,
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	.bEndpointAddress	= USB_DIR_IN,
16362306a36Sopenharmony_ci	.bmAttributes		= USB_ENDPOINT_XFER_BULK,
16462306a36Sopenharmony_ci};
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_cistatic struct usb_endpoint_descriptor obex_fs_ep_out_desc = {
16762306a36Sopenharmony_ci	.bLength		= USB_DT_ENDPOINT_SIZE,
16862306a36Sopenharmony_ci	.bDescriptorType	= USB_DT_ENDPOINT,
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	.bEndpointAddress	= USB_DIR_OUT,
17162306a36Sopenharmony_ci	.bmAttributes		= USB_ENDPOINT_XFER_BULK,
17262306a36Sopenharmony_ci};
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_cistatic struct usb_descriptor_header *fs_function[] = {
17562306a36Sopenharmony_ci	(struct usb_descriptor_header *) &obex_control_intf,
17662306a36Sopenharmony_ci	(struct usb_descriptor_header *) &obex_cdc_header_desc,
17762306a36Sopenharmony_ci	(struct usb_descriptor_header *) &obex_desc,
17862306a36Sopenharmony_ci	(struct usb_descriptor_header *) &obex_cdc_union_desc,
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	(struct usb_descriptor_header *) &obex_data_nop_intf,
18162306a36Sopenharmony_ci	(struct usb_descriptor_header *) &obex_data_intf,
18262306a36Sopenharmony_ci	(struct usb_descriptor_header *) &obex_fs_ep_in_desc,
18362306a36Sopenharmony_ci	(struct usb_descriptor_header *) &obex_fs_ep_out_desc,
18462306a36Sopenharmony_ci	NULL,
18562306a36Sopenharmony_ci};
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci/*-------------------------------------------------------------------------*/
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cistatic int obex_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	struct f_obex		*obex = func_to_obex(f);
19262306a36Sopenharmony_ci	struct usb_composite_dev *cdev = f->config->cdev;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	if (intf == obex->ctrl_id) {
19562306a36Sopenharmony_ci		if (alt != 0)
19662306a36Sopenharmony_ci			goto fail;
19762306a36Sopenharmony_ci		/* NOP */
19862306a36Sopenharmony_ci		dev_dbg(&cdev->gadget->dev,
19962306a36Sopenharmony_ci			"reset obex ttyGS%d control\n", obex->port_num);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	} else if (intf == obex->data_id) {
20262306a36Sopenharmony_ci		if (alt > 1)
20362306a36Sopenharmony_ci			goto fail;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci		if (obex->port.in->enabled) {
20662306a36Sopenharmony_ci			dev_dbg(&cdev->gadget->dev,
20762306a36Sopenharmony_ci				"reset obex ttyGS%d\n", obex->port_num);
20862306a36Sopenharmony_ci			gserial_disconnect(&obex->port);
20962306a36Sopenharmony_ci		}
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci		if (!obex->port.in->desc || !obex->port.out->desc) {
21262306a36Sopenharmony_ci			dev_dbg(&cdev->gadget->dev,
21362306a36Sopenharmony_ci				"init obex ttyGS%d\n", obex->port_num);
21462306a36Sopenharmony_ci			if (config_ep_by_speed(cdev->gadget, f,
21562306a36Sopenharmony_ci					       obex->port.in) ||
21662306a36Sopenharmony_ci			    config_ep_by_speed(cdev->gadget, f,
21762306a36Sopenharmony_ci					       obex->port.out)) {
21862306a36Sopenharmony_ci				obex->port.out->desc = NULL;
21962306a36Sopenharmony_ci				obex->port.in->desc = NULL;
22062306a36Sopenharmony_ci				goto fail;
22162306a36Sopenharmony_ci			}
22262306a36Sopenharmony_ci		}
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci		if (alt == 1) {
22562306a36Sopenharmony_ci			dev_dbg(&cdev->gadget->dev,
22662306a36Sopenharmony_ci				"activate obex ttyGS%d\n", obex->port_num);
22762306a36Sopenharmony_ci			gserial_connect(&obex->port, obex->port_num);
22862306a36Sopenharmony_ci		}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	} else
23162306a36Sopenharmony_ci		goto fail;
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	obex->cur_alt = alt;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	return 0;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_cifail:
23862306a36Sopenharmony_ci	return -EINVAL;
23962306a36Sopenharmony_ci}
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_cistatic int obex_get_alt(struct usb_function *f, unsigned intf)
24262306a36Sopenharmony_ci{
24362306a36Sopenharmony_ci	struct f_obex		*obex = func_to_obex(f);
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	return obex->cur_alt;
24662306a36Sopenharmony_ci}
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_cistatic void obex_disable(struct usb_function *f)
24962306a36Sopenharmony_ci{
25062306a36Sopenharmony_ci	struct f_obex	*obex = func_to_obex(f);
25162306a36Sopenharmony_ci	struct usb_composite_dev *cdev = f->config->cdev;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	dev_dbg(&cdev->gadget->dev, "obex ttyGS%d disable\n", obex->port_num);
25462306a36Sopenharmony_ci	gserial_disconnect(&obex->port);
25562306a36Sopenharmony_ci}
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci/*-------------------------------------------------------------------------*/
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_cistatic void obex_connect(struct gserial *g)
26062306a36Sopenharmony_ci{
26162306a36Sopenharmony_ci	struct f_obex		*obex = port_to_obex(g);
26262306a36Sopenharmony_ci	struct usb_composite_dev *cdev = g->func.config->cdev;
26362306a36Sopenharmony_ci	int			status;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	status = usb_function_activate(&g->func);
26662306a36Sopenharmony_ci	if (status)
26762306a36Sopenharmony_ci		dev_dbg(&cdev->gadget->dev,
26862306a36Sopenharmony_ci			"obex ttyGS%d function activate --> %d\n",
26962306a36Sopenharmony_ci			obex->port_num, status);
27062306a36Sopenharmony_ci}
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_cistatic void obex_disconnect(struct gserial *g)
27362306a36Sopenharmony_ci{
27462306a36Sopenharmony_ci	struct f_obex		*obex = port_to_obex(g);
27562306a36Sopenharmony_ci	struct usb_composite_dev *cdev = g->func.config->cdev;
27662306a36Sopenharmony_ci	int			status;
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	status = usb_function_deactivate(&g->func);
27962306a36Sopenharmony_ci	if (status)
28062306a36Sopenharmony_ci		dev_dbg(&cdev->gadget->dev,
28162306a36Sopenharmony_ci			"obex ttyGS%d function deactivate --> %d\n",
28262306a36Sopenharmony_ci			obex->port_num, status);
28362306a36Sopenharmony_ci}
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci/*-------------------------------------------------------------------------*/
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci/* Some controllers can't support CDC OBEX ... */
28862306a36Sopenharmony_cistatic inline bool can_support_obex(struct usb_configuration *c)
28962306a36Sopenharmony_ci{
29062306a36Sopenharmony_ci	/* Since the first interface is a NOP, we can ignore the
29162306a36Sopenharmony_ci	 * issue of multi-interface support on most controllers.
29262306a36Sopenharmony_ci	 *
29362306a36Sopenharmony_ci	 * Altsettings are mandatory, however...
29462306a36Sopenharmony_ci	 */
29562306a36Sopenharmony_ci	if (!gadget_is_altset_supported(c->cdev->gadget))
29662306a36Sopenharmony_ci		return false;
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	/* everything else is *probably* fine ... */
29962306a36Sopenharmony_ci	return true;
30062306a36Sopenharmony_ci}
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_cistatic int obex_bind(struct usb_configuration *c, struct usb_function *f)
30362306a36Sopenharmony_ci{
30462306a36Sopenharmony_ci	struct usb_composite_dev *cdev = c->cdev;
30562306a36Sopenharmony_ci	struct f_obex		*obex = func_to_obex(f);
30662306a36Sopenharmony_ci	struct usb_string	*us;
30762306a36Sopenharmony_ci	int			status;
30862306a36Sopenharmony_ci	struct usb_ep		*ep;
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	if (!can_support_obex(c))
31162306a36Sopenharmony_ci		return -EINVAL;
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	us = usb_gstrings_attach(cdev, obex_strings,
31462306a36Sopenharmony_ci				 ARRAY_SIZE(obex_string_defs));
31562306a36Sopenharmony_ci	if (IS_ERR(us))
31662306a36Sopenharmony_ci		return PTR_ERR(us);
31762306a36Sopenharmony_ci	obex_control_intf.iInterface = us[OBEX_CTRL_IDX].id;
31862306a36Sopenharmony_ci	obex_data_nop_intf.iInterface = us[OBEX_DATA_IDX].id;
31962306a36Sopenharmony_ci	obex_data_intf.iInterface = us[OBEX_DATA_IDX].id;
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	/* allocate instance-specific interface IDs, and patch descriptors */
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	status = usb_interface_id(c, f);
32462306a36Sopenharmony_ci	if (status < 0)
32562306a36Sopenharmony_ci		goto fail;
32662306a36Sopenharmony_ci	obex->ctrl_id = status;
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	obex_control_intf.bInterfaceNumber = status;
32962306a36Sopenharmony_ci	obex_cdc_union_desc.bMasterInterface0 = status;
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	status = usb_interface_id(c, f);
33262306a36Sopenharmony_ci	if (status < 0)
33362306a36Sopenharmony_ci		goto fail;
33462306a36Sopenharmony_ci	obex->data_id = status;
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	obex_data_nop_intf.bInterfaceNumber = status;
33762306a36Sopenharmony_ci	obex_data_intf.bInterfaceNumber = status;
33862306a36Sopenharmony_ci	obex_cdc_union_desc.bSlaveInterface0 = status;
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	/* allocate instance-specific endpoints */
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci	status = -ENODEV;
34362306a36Sopenharmony_ci	ep = usb_ep_autoconfig(cdev->gadget, &obex_fs_ep_in_desc);
34462306a36Sopenharmony_ci	if (!ep)
34562306a36Sopenharmony_ci		goto fail;
34662306a36Sopenharmony_ci	obex->port.in = ep;
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	ep = usb_ep_autoconfig(cdev->gadget, &obex_fs_ep_out_desc);
34962306a36Sopenharmony_ci	if (!ep)
35062306a36Sopenharmony_ci		goto fail;
35162306a36Sopenharmony_ci	obex->port.out = ep;
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_ci	/* support all relevant hardware speeds... we expect that when
35462306a36Sopenharmony_ci	 * hardware is dual speed, all bulk-capable endpoints work at
35562306a36Sopenharmony_ci	 * both speeds
35662306a36Sopenharmony_ci	 */
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	obex_hs_ep_in_desc.bEndpointAddress =
35962306a36Sopenharmony_ci		obex_fs_ep_in_desc.bEndpointAddress;
36062306a36Sopenharmony_ci	obex_hs_ep_out_desc.bEndpointAddress =
36162306a36Sopenharmony_ci		obex_fs_ep_out_desc.bEndpointAddress;
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	status = usb_assign_descriptors(f, fs_function, hs_function, NULL,
36462306a36Sopenharmony_ci					NULL);
36562306a36Sopenharmony_ci	if (status)
36662306a36Sopenharmony_ci		goto fail;
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	dev_dbg(&cdev->gadget->dev, "obex ttyGS%d: IN/%s OUT/%s\n",
36962306a36Sopenharmony_ci		obex->port_num,
37062306a36Sopenharmony_ci		obex->port.in->name, obex->port.out->name);
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	return 0;
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_cifail:
37562306a36Sopenharmony_ci	ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status);
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	return status;
37862306a36Sopenharmony_ci}
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_cistatic inline struct f_serial_opts *to_f_serial_opts(struct config_item *item)
38162306a36Sopenharmony_ci{
38262306a36Sopenharmony_ci	return container_of(to_config_group(item), struct f_serial_opts,
38362306a36Sopenharmony_ci			    func_inst.group);
38462306a36Sopenharmony_ci}
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_cistatic void obex_attr_release(struct config_item *item)
38762306a36Sopenharmony_ci{
38862306a36Sopenharmony_ci	struct f_serial_opts *opts = to_f_serial_opts(item);
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	usb_put_function_instance(&opts->func_inst);
39162306a36Sopenharmony_ci}
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_cistatic struct configfs_item_operations obex_item_ops = {
39462306a36Sopenharmony_ci	.release	= obex_attr_release,
39562306a36Sopenharmony_ci};
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_cistatic ssize_t f_obex_port_num_show(struct config_item *item, char *page)
39862306a36Sopenharmony_ci{
39962306a36Sopenharmony_ci	return sprintf(page, "%u\n", to_f_serial_opts(item)->port_num);
40062306a36Sopenharmony_ci}
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ciCONFIGFS_ATTR_RO(f_obex_, port_num);
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_cistatic struct configfs_attribute *acm_attrs[] = {
40562306a36Sopenharmony_ci	&f_obex_attr_port_num,
40662306a36Sopenharmony_ci	NULL,
40762306a36Sopenharmony_ci};
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_cistatic const struct config_item_type obex_func_type = {
41062306a36Sopenharmony_ci	.ct_item_ops	= &obex_item_ops,
41162306a36Sopenharmony_ci	.ct_attrs	= acm_attrs,
41262306a36Sopenharmony_ci	.ct_owner	= THIS_MODULE,
41362306a36Sopenharmony_ci};
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_cistatic void obex_free_inst(struct usb_function_instance *f)
41662306a36Sopenharmony_ci{
41762306a36Sopenharmony_ci	struct f_serial_opts *opts;
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci	opts = container_of(f, struct f_serial_opts, func_inst);
42062306a36Sopenharmony_ci	gserial_free_line(opts->port_num);
42162306a36Sopenharmony_ci	kfree(opts);
42262306a36Sopenharmony_ci}
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_cistatic struct usb_function_instance *obex_alloc_inst(void)
42562306a36Sopenharmony_ci{
42662306a36Sopenharmony_ci	struct f_serial_opts *opts;
42762306a36Sopenharmony_ci	int ret;
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
43062306a36Sopenharmony_ci	if (!opts)
43162306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	opts->func_inst.free_func_inst = obex_free_inst;
43462306a36Sopenharmony_ci	ret = gserial_alloc_line_no_console(&opts->port_num);
43562306a36Sopenharmony_ci	if (ret) {
43662306a36Sopenharmony_ci		kfree(opts);
43762306a36Sopenharmony_ci		return ERR_PTR(ret);
43862306a36Sopenharmony_ci	}
43962306a36Sopenharmony_ci	config_group_init_type_name(&opts->func_inst.group, "",
44062306a36Sopenharmony_ci				    &obex_func_type);
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	return &opts->func_inst;
44362306a36Sopenharmony_ci}
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_cistatic void obex_free(struct usb_function *f)
44662306a36Sopenharmony_ci{
44762306a36Sopenharmony_ci	struct f_obex *obex;
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ci	obex = func_to_obex(f);
45062306a36Sopenharmony_ci	kfree(obex);
45162306a36Sopenharmony_ci}
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_cistatic void obex_unbind(struct usb_configuration *c, struct usb_function *f)
45462306a36Sopenharmony_ci{
45562306a36Sopenharmony_ci	usb_free_all_descriptors(f);
45662306a36Sopenharmony_ci}
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_cistatic struct usb_function *obex_alloc(struct usb_function_instance *fi)
45962306a36Sopenharmony_ci{
46062306a36Sopenharmony_ci	struct f_obex	*obex;
46162306a36Sopenharmony_ci	struct f_serial_opts *opts;
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci	/* allocate and initialize one new instance */
46462306a36Sopenharmony_ci	obex = kzalloc(sizeof(*obex), GFP_KERNEL);
46562306a36Sopenharmony_ci	if (!obex)
46662306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	opts = container_of(fi, struct f_serial_opts, func_inst);
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_ci	obex->port_num = opts->port_num;
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	obex->port.connect = obex_connect;
47362306a36Sopenharmony_ci	obex->port.disconnect = obex_disconnect;
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	obex->port.func.name = "obex";
47662306a36Sopenharmony_ci	/* descriptors are per-instance copies */
47762306a36Sopenharmony_ci	obex->port.func.bind = obex_bind;
47862306a36Sopenharmony_ci	obex->port.func.unbind = obex_unbind;
47962306a36Sopenharmony_ci	obex->port.func.set_alt = obex_set_alt;
48062306a36Sopenharmony_ci	obex->port.func.get_alt = obex_get_alt;
48162306a36Sopenharmony_ci	obex->port.func.disable = obex_disable;
48262306a36Sopenharmony_ci	obex->port.func.free_func = obex_free;
48362306a36Sopenharmony_ci	obex->port.func.bind_deactivated = true;
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci	return &obex->port.func;
48662306a36Sopenharmony_ci}
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ciDECLARE_USB_FUNCTION_INIT(obex, obex_alloc_inst, obex_alloc);
48962306a36Sopenharmony_ciMODULE_AUTHOR("Felipe Balbi");
49062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
491