blob: 4861a30acf1a0f65d980dc5f5c2fad462c073665 [file] [log] [blame]
/*
* 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)) {
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;
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;
}