aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/gadget/multi_config.c
blob: 2e4f52e6bdc42d2cc85bc77f9bad605f45cfc8bd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
/*
 * File Name : multi_config.c
 *
 * Virtual multi configuration utilities for composite USB gadgets.
 * This utilitie can support variable interface for variable Host PC.
 *
 * Copyright (C) 2011 Samsung Electronics
 * Author: SoonYong, Cho <soonyong.cho@samsung.com>
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include "multi_config.h"

static int multi; /* current configuration */
static int is_multi; /* Is multi configuration available ? */
static int stringMode = OTHER_REQUEST;
static int interfaceCount;

/* Description  : Set configuration number
 * Parameter    : unsigned num (host request)
 * Return value : always return 0 (It's virtual multiconfiguration)
 */
unsigned set_config_number(unsigned num)
{
	if (num != 0)
		USB_DBG_ESS("multi config_num=%d(zero base)\n", num);
	else
		USB_DBG_ESS("single config\n");

	multi = num;	/* save config number from Host request */
	return 0;	/* always return 0 config */
}

/* Description  : Get configuration number
 * Return value : virtual multiconfiguration number (zero base)
 */
int get_config_number(void)
{
	USB_DBG("multi=%d\n", multi);

	return multi;
}

/* Description  : Check configuration number
 * Parameter    : unsigned num (host request)
 * Return value : 1 (true  : virtual multi configuraiton)
		  0 (false : normal configuration)
 */
int check_config(unsigned num)
{
	USB_DBG("num=%d, multi=%d\n", num, multi);
	/* multi is zero base, but num is 1 ase */
	if (num && num == multi + 1) {
		USB_DBG("Use virtual multi configuration\n");
		return 1;
	} else {
		USB_DBG("normal configuration\n");
		return 0;
	}
}

/* Description  : Search number of configuration including virtual configuration
 * Parameter    : usb_configuration *c (referenced configuration)
		  unsigned count (real number of configuration)
 * Return value : virtual or real number of configuration
 */
unsigned count_multi_config(struct usb_configuration *c, unsigned count)
{
	int				f_first = 0;
	int				f_second = 0;
	int				f_exception = 0;
	struct usb_function		*f;
	is_multi = 0;

	if (!c) {
		USB_DBG("usb_configuration is not valid\n");
		return 0;
	}

	list_for_each_entry(f, &c->functions, list) {
		if (!strcmp(f->name, MULTI_FUNCTION_1)) {
			USB_DBG("%s +\n", MULTI_FUNCTION_1);
			f_first = 1;
		} else if (!strcmp(f->name, MULTI_FUNCTION_2)) {
			USB_DBG("%s +\n", MULTI_FUNCTION_2);
			f_second = 1;
		} else if (!strcmp(f->name, MULTI_EXCEPTION_FUNCTION)) {
			USB_DBG("exception %s +\n", MULTI_EXCEPTION_FUNCTION);
			f_exception = 1;
		}
	}

	if (f_first && f_second && !f_exception) {
		USB_DBG_ESS("ready multi\n");
		is_multi = 1;
		return 2;
	}
	return count;
}

/* Description  : Is multi configuration available ?
 * Return value : 1 (true), 0 (false)
 */
int is_multi_configuration(void)
{
	USB_DBG("= %d\n", is_multi);
	return is_multi;
}

/* Description  : Check function to skip for multi configuration
 * Parameter    : char* name (function name)
 * Return value : 0 (not available), 1 (available)
 */
int is_available_function(const char *name)
{
	if (is_multi_configuration()) {
		USB_DBG("multi case\n");
		if (!multi) {
			if (!strcmp(name, MAIN_FUNCTION)) {
				USB_DBG("%s is available.\n",
						MAIN_FUNCTION);
				return 1;
			}
			return 0; /* anothor function is not available */
		} else {
			USB_DBG("multi=%d all available\n", multi);
		}
	}
	return 1; /* if single configuration, every function is available */
}

/* Description  : Change configuration using virtual multi configuration.
 * Parameter    : struct usb_funciton f (to be changed function interface)
		  void *next (next means usb req->buf)
		  int len (length for to fill buffer)
		  struct usb_configuration *config
		  (To reference interface array of current config)
		  enum usb_device_speed speed (usb speed)
 * Return value : "ret < 0" means fillbuffer function is failed.
 */
int change_conf(struct usb_function *f,
		void *next, int len,
		struct usb_configuration *config,
		enum usb_device_speed speed)
{
	u8 *dest;
	int status = 0;
	struct usb_descriptor_header *descriptor;
	struct usb_interface_descriptor *intf;
	int index_intf = 0;
	int change_intf = 0;
	struct usb_descriptor_header **descriptors;

	USB_DBG("f->%s process multi\n", f->name);

	if (!f || !config || !next) {
		USB_DBG_ESS("one of f, config, next is not valid\n");
		return -EFAULT;
	}
	if (speed == USB_SPEED_HIGH)
		descriptors = f->hs_descriptors;
	else
		descriptors = f->descriptors;
	if (!descriptors) {
		USB_DBG_ESS("descriptor is not available\n");
		return -EFAULT;
	}

	if (f->set_config_desc)
		f->set_config_desc(stringMode);

	/* set interface numbers dynamically */
	dest = next;

	while ((descriptor = *descriptors++) != NULL) {
		intf = (struct usb_interface_descriptor *)dest;
		if (intf->bDescriptorType == USB_DT_INTERFACE) {
			if (intf->bAlternateSetting == 0) {
				intf->bInterfaceNumber = interfaceCount++;
				USB_DBG("a=0 intf->bInterfaceNumber=%d\n",
						intf->bInterfaceNumber);
			} else {
				intf->bInterfaceNumber = interfaceCount - 1;
				USB_DBG("a!=0 intf->bInterfaceNumber=%d\n",
						intf->bInterfaceNumber);
			}
			config->interface
				[intf->bInterfaceNumber]
				= f;
			if (f->set_intf_num) {
				change_intf = 1;
				f->set_intf_num(f,
						intf->bInterfaceNumber,
						index_intf++);
			}
		}
		dest += intf->bLength;
	}

	if (change_intf) {
		if (speed == USB_SPEED_HIGH)
			descriptors = f->hs_descriptors;
		else
			descriptors = f->descriptors;
		status = usb_descriptor_fillbuf(
				next, len,
				(const struct usb_descriptor_header **)
				descriptors);
		if (status < 0) {
			USB_DBG_ESS("usb_descriptor_fillbuf failed\n");
			return status;
		}
	}

	return status;
}

/* Description  : Set interface count
 * Parameter    : struct usb_configuration *config
 *		  (To reference interface array of current config)
 *		  struct usb_config_descriptor *c
 *		  (number of interfaces)
 * Return value : void
 */
void set_interface_count(struct usb_configuration *config,
	struct usb_config_descriptor *c)
{
	USB_DBG_ESS("next_interface_id=%d\n", interfaceCount);
	config->next_interface_id = interfaceCount;
	config->interface[interfaceCount] = 0;
	c->bNumInterfaces = interfaceCount;
	interfaceCount = 0;
	return ;
}

/* Description  : Set string mode
 *		  This mode will be used for deciding other interface.
 * Parameter    : u16 w_length
 *		- 2 means MAC request.
 *		- Windows and Linux PC always request 255 size.
 */
void set_string_mode(u16 w_length)
{
	if (w_length == 2) {
		USB_DBG("mac request\n");
		stringMode = MAC_REQUEST;
	} else if (w_length == 0) {
		USB_DBG("initialize string mode\n");
		stringMode = OTHER_REQUEST;
	}
}