276 lines
7.2 KiB
C
276 lines
7.2 KiB
C
|
/*
|
||
|
* 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 <linux/module.h>
|
||
|
#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 (is_multi_configuration()) {
|
||
|
USB_DBG_ESS("multi config_num=%d(zero base)\n", num);
|
||
|
if (num < MAX_MULTI_CONFIG_NUM)
|
||
|
multi = num; /* save config number from Host request */
|
||
|
} else {
|
||
|
USB_DBG_ESS("single config num=%d\n", num);
|
||
|
multi = 0; /* single config */
|
||
|
}
|
||
|
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) ||
|
||
|
!strcmp(f->name, MULTI_EXCEPTION_FUNCTION_F_FS)) {
|
||
|
USB_DBG("exception %s +\n", f->name );
|
||
|
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;
|
||
|
|
||
|
if (!f || !config || !next) {
|
||
|
USB_DBG_ESS("one of f, config, next is not valid\n");
|
||
|
return -EFAULT;
|
||
|
}
|
||
|
|
||
|
USB_DBG("f->%s process multi\n", f->name);
|
||
|
|
||
|
if (speed == USB_SPEED_HIGH)
|
||
|
descriptors = f->hs_descriptors;
|
||
|
else
|
||
|
descriptors = f->fs_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->fs_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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Description : Get Host OS type
|
||
|
* Return value : type - u16
|
||
|
* - 0 : MAC PC
|
||
|
* - 1 : Windows and Linux PC
|
||
|
*/
|
||
|
u16 get_host_os_type(void)
|
||
|
{
|
||
|
return stringMode;
|
||
|
}
|