www.pudn.com > Linux2410_device.rar > usbd-func.c


/*
 * linux/drivers/usbd/usbd-func.c - USB Function support
 *
 * Copyright (c) 2000, 2001, 2002 Lineo
 * Copyright (c) 2001 Hewlett Packard
 *
 * By: 
 *      Stuart Lynne , 
 *      Tom Rushworth , 
 *      Bruce Balden 
 *
 * Changes copyright (c) 2003 MontaVista Software, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */


#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "usbd.h"
#include "usbd-debug.h"
#include "usbd-func.h"
#include "usbd-bus.h"
#include "usbd-inline.h"


#define LANGID_ENGLISH          "\011"
#define LANGID_US_ENGLISH       "\004"

//#define LANGIDs  LANGID_US_ENGLISH LANGID_ENGLISH LANGID_US_ENGLISH LANGID_ENGLISH LANGID_US_ENGLISH LANGID_ENGLISH 
#define LANGIDs  LANGID_US_ENGLISH LANGID_ENGLISH

//#define LANGIDs  LANGID_ENGLISH LANGID_US_ENGLISH LANGID_US_ENGLISH

extern int maxstrings;
extern struct usb_string_descriptor **usb_strings;
extern int usb_devices;
extern struct usb_function_driver ep0_driver;

extern struct list_head function_drivers;
extern struct list_head devices;

static __inline__ struct usb_function_driver *list_entry_func (const struct list_head *le)
{
	return list_entry (le, struct usb_function_driver, drivers);
}

static __inline__ struct usb_device_instance *list_entry_device (const struct list_head *le)
{
	return list_entry (le, struct usb_device_instance, devices);
}

void lkfree (void *p)
{
	if (p) {
		kfree (p);
	}
}

int dbgflg_usbdfd_init;
EXPORT_SYMBOL(dbgflg_usbdfd_init);

extern int registered_functions;
extern int registered_devices;


#define dbg_init(lvl,fmt,args...) dbgPRINT(dbgflg_usbdfd_init,lvl,fmt,##args)



/* *
 * usbd_alloc_string_zero - allocate a string descriptor and return index number
 * @str: pointer to string
 *
 * Find an empty slot in index string array, create a corresponding descriptor
 * and return the slot number.
 */
__devinit __u8 usbd_alloc_string_zero (char *str)
{
	struct usb_string_descriptor *string;
	__u8 bLength;
	__u16 *wData;

	dbg_init (1, "%02x %02x %02x %02x", str[0], str[1], str[2], str[3]);

	if (usb_strings[0] == NULL) {

		bLength = sizeof (struct usb_string_descriptor) + strlen (str);
		dbg_init (1, "blength: %d strlen: %d", bLength, strlen (str));

		if (!(string = ckmalloc (bLength, GFP_KERNEL))) {
			return 0;
		}

		string->bLength = bLength;
		string->bDescriptorType = USB_DT_STRING;

		for (wData = string->wData; *str;) {
			*wData = cpu_to_le16((__u16)((str[0] << 8 | str[1])));
			dbg_init (1, "%p %04x %04x", wData, *wData,
				cpu_to_le16((__u16)str[0] << 8 | str[1]));
			str += 2;
			wData++;
		}

		// store in string index array
		usb_strings[0] = string;
	}
	return 0;
}

/* *
 * usbd_alloc_string - allocate a string descriptor and return index number
 * @str: pointer to string
 *
 * Find an empty slot in index string array, create a corresponding descriptor
 * and return the slot number.
 */
__devinit __u8 usbd_alloc_string (char *str)
{
	int i;
	struct usb_string_descriptor *string;
	__u8 bLength;
	__u16 *wData;

	//return 0;

	if (!str || !strlen (str)) {
		return 0;
	}
	dbg_init (1, "%d: %s", maxstrings, str);

	// find an empty string descriptor slot
	for (i = 1; i < maxstrings; i++) {

		if (usb_strings[i] == NULL) {


			bLength = sizeof (struct usb_string_descriptor) + 2 * strlen (str);

			dbg_init (1, "%s -> %d strlen: %d bLength: %d", str, i, strlen (str), bLength);

			if (!(string = ckmalloc (bLength, GFP_KERNEL))) {
				return 0;
			}

			string->bLength = bLength;
			string->bDescriptorType = USB_DT_STRING;

			for (wData = string->wData; *str;) {
				*wData++ = cpu_to_le16((__u16)(*str++));
			}

			// store in string index array
			usb_strings[i] = string;
			return i;
		}
#if 1
		else {
			char *cp = str;
			int j;
			for (j = 0; (j < ((usb_strings[i]->bLength - 1) / 2)) && ((usb_strings[i]->wData[j] & 0xff) == cp[j]); j++) {

				dbg_init (4, "checking for duplicate %d:%d: strlen: %d bLength: %d bLength-1/2: %d wData[%04x] cp[%02x]",
					  i, j, strlen (str), usb_strings[i]->bLength, (usb_strings[i]->bLength - 1) / 2, (usb_strings[i]->wData[j]), cp[j]
				    );
			}
			dbg_init (1, "finished               %d:%d: strlen: %d bLength: %d bLength-1/2: %d wData[%04x] cp[%02x]",
				  i, j, strlen (str), usb_strings[i]->bLength, (usb_strings[i]->bLength - 1) / 2, (usb_strings[i]->wData[j]), cp[j]
			    );


			if ((j >= ((usb_strings[i]->bLength - 1) / 2)) /*&& ((usb_strings[i]->wData[j]&0xff) == cp[j]) */ ) {
				dbg_init (1, "%s -> %d (duplicate)", str, i);
				return i;
			}
		}
#endif
	}
	return 0;
}


/* *
 * usbd_dealloc_string - deallocate a string descriptor
 * @index: index into strings array to deallocte
 *
 * Find and remove an allocated string.
 */
void __devexit usbd_dealloc_string (__u8 index)
{
	struct usb_string_descriptor *string;

	dbg_init (1, "%d", index);

	if ((index > 0) && (index < maxstrings) && (string = usb_strings[index])) {
		usb_strings[index] = NULL;
		dbg_init (1, "%p", string);
		lkfree (string);
	}
}

__u8 device_descriptor_sizes[] = {
	0,
	sizeof (struct usb_device_descriptor),
	sizeof (struct usb_configuration_descriptor),
	sizeof (struct usb_string_descriptor),
	sizeof (struct usb_interface_descriptor),
	sizeof (struct usb_endpoint_descriptor)
};

__u8 class_descriptor_sizes[] = {
	sizeof (struct usb_class_header_function_descriptor),	// 0x0
	sizeof (struct usb_class_call_management_descriptor),	// 0x1
	sizeof (struct usb_class_abstract_control_descriptor),	// 0x2
	sizeof (struct usb_class_direct_line_descriptor),	// 0x3
	sizeof (struct usb_class_telephone_ringer_descriptor),	// 0x4
	sizeof (struct usb_class_telephone_operational_descriptor),	// 0x5
	sizeof (struct usb_class_union_function_descriptor),	// 0x6
	sizeof (struct usb_class_country_selection_descriptor),	// 0x7
	sizeof (struct usb_class_telephone_operational_descriptor),	// 0x8
	sizeof (struct usb_class_usb_terminal_descriptor),	// 0x9
	sizeof (struct usb_class_network_channel_descriptor),	// 0xa
	sizeof (struct usb_class_protocol_unit_function_descriptor),	// 0xb
	sizeof (struct usb_class_extension_unit_descriptor),	// 0xc
	sizeof (struct usb_class_multi_channel_descriptor),	// 0xd
	sizeof (struct usb_class_capi_control_descriptor),	// 0xe
	sizeof (struct usb_class_ethernet_networking_descriptor),	// 0xf
	sizeof (struct usb_class_atm_networking_descriptor),	// 0x10
	0,			// 0x11
	sizeof (struct usb_class_mdlm_descriptor),	// 0x12
	sizeof (struct usb_class_mdlmd_descriptor),	// 0x13

};


/* *
 * usbd_alloc_descriptor - allocate a usb descriptor
 * bDescriptorType 
 *
 * Allocate and initialize a generic usb descriptor.
 */
__devinit static void *usbd_alloc_descriptor (__u8 bDescriptorType, __u8 bDescriptorSubtype, __u8 elements)
{
	struct usb_descriptor *descriptor;
	int length;


	dbg_init (1, "type: %d subtype: %d elements: %d", bDescriptorType, bDescriptorSubtype, elements);

	switch (bDescriptorType) {
	case USB_DT_DEVICE:
	case USB_DT_CONFIG:
	case USB_DT_INTERFACE:
	case USB_DT_ENDPOINT:
		length = device_descriptor_sizes[bDescriptorType];
		break;

	case CS_INTERFACE:
		switch (bDescriptorSubtype) {

		case USB_ST_HEADER:
		case USB_ST_CMF:
		case USB_ST_ACMF:
		case USB_ST_DLMF:
		case USB_ST_TRF:
		case USB_ST_TCLF:

		case USB_ST_NCT:
		case USB_ST_MCMF:
		case USB_ST_CCMF:
		case USB_ST_ENF:
		case USB_ST_ATMNF:
		case USB_ST_MDLM:
			length = class_descriptor_sizes[bDescriptorSubtype];
			break;

		case USB_ST_UF:
		case USB_ST_CSF:
		case USB_ST_TOMF:
		case USB_ST_USBTF:
		case USB_ST_PUF:
		case USB_ST_EUF:
		case USB_ST_MDLMD:
			length = class_descriptor_sizes[bDescriptorSubtype] + elements;	// XXX check me
			break;

		default:
			return NULL;

		}
		break;

	case USB_DT_HID:
		length = sizeof(struct usb_class_hid_descriptor) + elements;
		break;
	case USB_DT_REPORT:
		length = sizeof(struct usb_class_report_descriptor) + elements;
		break;

	case USB_DT_STRING:
	default:
		return NULL;
	}

	if (!(descriptor = ckmalloc (length, GFP_KERNEL))) {
		return NULL;
	}

	switch (bDescriptorType) {
	case CS_INTERFACE:
		descriptor->descriptor.generic.bDescriptorSubtype = bDescriptorSubtype;

	case USB_DT_DEVICE:
	case USB_DT_CONFIG:
	case USB_DT_INTERFACE:
	case USB_DT_ENDPOINT:
	case USB_DT_HID:
	case USB_DT_REPORT:
		descriptor->descriptor.generic.bLength = length;
		descriptor->descriptor.generic.bDescriptorType = bDescriptorType;
		break;


	case USB_DT_STRING:
		break;
	}

	dbg_init (1, "descriptor: %p length: %d", descriptor, length);

	return descriptor;
}


/* *
 * usbd_dealloc_descriptor - deallocate a usb descriptor
 * @descriptor: pointer to usb descriptor to deallocate
 *
 * Deallocate a descriptor, first deallocate the index string descriptors.
 */
static __devexit void usbd_dealloc_descriptor (struct usb_descriptor *descriptor)
{
	dbg_init (2, "descriptor: %p type: %d", descriptor, descriptor->descriptor.generic.bDescriptorType);
	if (descriptor) {
		switch (descriptor->descriptor.generic.bDescriptorType) {
		case USB_DT_DEVICE:
			dbg_init (2, "USB_DT_DEVICE");
			usbd_dealloc_string (descriptor->descriptor.device.iManufacturer);
			usbd_dealloc_string (descriptor->descriptor.device.iProduct);
			usbd_dealloc_string (descriptor->descriptor.device.iManufacturer);
			break;
		case USB_DT_CONFIG:
			dbg_init (2, "USB_DT_CONFIG");
			usbd_dealloc_string (descriptor->descriptor.configuration.iConfiguration);
			break;
		case USB_DT_INTERFACE:
			dbg_init (2, "USB_DT_INTERFACE");
			usbd_dealloc_string (descriptor->descriptor.interface.iInterface);
			break;
		case CS_INTERFACE:
			dbg_init (2, "class descriptor: %p type: %d subtype: %d", descriptor, descriptor->descriptor.generic.bDescriptorType, descriptor->descriptor.generic.bDescriptorSubtype);
			dbg_init (2, "USB_CS_INTERFACE");
			{
				struct usb_class_descriptor *class_descriptor = (struct usb_class_descriptor *) descriptor;
				switch (descriptor->descriptor.generic.bDescriptorSubtype) {

				case USB_ST_HEADER:
				case USB_ST_CMF:
				case USB_ST_ACMF:
				case USB_ST_DLMF:
				case USB_ST_TRF:
				case USB_ST_TCLF:
				case USB_ST_UF:
				case USB_ST_CSF:
				case USB_ST_TOMF:
				case USB_ST_USBTF:
				case USB_ST_NCT:
				case USB_ST_PUF:
				case USB_ST_EUF:
				case USB_ST_MCMF:
				case USB_ST_CCMF:
					break;
				case USB_ST_ENF:
					dbg_init (2, "USB_ST_ENF");
					usbd_dealloc_string (class_descriptor->descriptor.ethernet_networking.iMACAddress);
					break;
				case USB_ST_ATMNF:
					break;
				}
			}
			break;
		case USB_DT_HID:
			dbg_init(2,"USB_DT_HID");
			break;
		case USB_DT_REPORT:
			dbg_init(2,"USB_DT_REPORT");
			break;
		}
		lkfree (descriptor);
	}
}

/* usb-device USB FUNCTION generic functions ************************************************* */


/* *
* alloc_function_classes - allocate class descriptor array
* @classes: number of classes
* @class_description_array: pointer to an array of class descriptions
*
 * Return a pointer to an array of pointers to class descriptors. The descriptors
 * will be filled in with information from the class description array.
 * 
 * Returning NULL will cause the caller to cleanup all previously allocated memory.
 */
__devinit static struct usb_class_descriptor **alloc_function_classes (int classes, struct usb_class_description *class_description_array)
{
	int class;
	struct usb_class_descriptor **classes_descriptor_array;

	dbg_init (1, "classes: %d", classes);

	if (!classes || !class_description_array) {
		return NULL;
	}
	// allocate the class descriptor array
	if (!(classes_descriptor_array = ckmalloc (sizeof (struct usb_class_descriptor *) * classes, GFP_KERNEL))) {
		return NULL;
	}
	for (class = 0; class < classes; class++) {
		struct usb_class_description *class_description = class_description_array + class;
		struct usb_class_descriptor *class_descriptor;
		//int elements;

		dbg_init(1,"class: %d Type: %d Subtype: %d elements: %d",
			 class, class_description->bDescriptorType,
			 class_description->bDescriptorSubtype,
			 class_description->elements);

		// allocate an class descriptor to use
		if (!(class_descriptor =
		      usbd_alloc_descriptor(
			      class_description->bDescriptorType,
			      class_description->bDescriptorSubtype,
			      class_description->elements))) {
			dbg_init (0, "usbd_alloc_descriptor failed");
			return NULL;
		}
		if (class_description->bDescriptorType != CS_INTERFACE) {
			switch (class_description->bDescriptorType) {
			case USB_DT_HID:
				class_descriptor->descriptor.hid.bcdCDC =
					cpu_to_le16(CLASS_HID_BCD_VERSION);
				class_descriptor->descriptor.hid.bCountryCode =
					class_description->description.hid.bCountryCode;
				class_descriptor->descriptor.hid.bNumDescriptors = 1;
				class_descriptor->descriptor.hid.bDescriptorType0 =
					class_description->description.hid.bDescriptorType;
				class_descriptor->descriptor.hid.wDescriptorLength0 =
					cpu_to_le16(class_description->description.hid.wDescriptorLength);
				break;
			}
			// save in class descriptor array
			classes_descriptor_array[class] = class_descriptor;
			continue;
		}
		// CDC class
		switch (class_description->bDescriptorSubtype) {

		case USB_ST_HEADER:
			class_descriptor->descriptor.header_function.bcdCDC = cpu_to_le16 (CLASS_BCD_VERSION);
			break;
		case USB_ST_CMF:
		case USB_ST_ACMF:
			class_descriptor->descriptor.abstract_control.bmCapabilities = class_description->description.abstract_control.bmCapabilities;
			break;
		case USB_ST_DLMF:
		case USB_ST_TRF:
		case USB_ST_TCLF:
			// XXX generalize USB_ST_UF here
			dbg_init (0, "NOT IMPLEMENTED");
			break;

		case USB_ST_UF:

			dbg_init (1, "USB_ST_UF: bMasterInterface: %02x", class_description->description.union_function.bMasterInterface);
			dbg_init (1, "USB_ST_UF: bSlaveInterface[0]: %02x", class_description->description.union_function.bSlaveInterface[0]);

			class_descriptor->descriptor.union_function.bMasterInterface = class_description->description.union_function.bMasterInterface;

			class_descriptor->descriptor.union_function.bSlaveInterface0[0] = class_description->description.union_function.bSlaveInterface[0];
#if 0
			for (elements = 0; elements < class_description->elements; elements++) {

				dbg_init (1, "USB_ST_UF: copying bSlaveInterface[%d] %02x", elements, class_description->description.union_function.bSlaveInterface[elements]);

				class_descriptor->descriptor.union_function.bSlaveInterface0[elements] = class_description->description.union_function.bSlaveInterface[elements];
			}
#endif
			break;
		case USB_ST_CSF:
		case USB_ST_TOMF:
		case USB_ST_USBTF:
		case USB_ST_NCT:
		case USB_ST_PUF:
		case USB_ST_EUF:
		case USB_ST_MCMF:
		case USB_ST_CCMF:
			break;
		case USB_ST_ENF:
			class_descriptor->descriptor.ethernet_networking.bmEthernetStatistics = class_description->description.ethernet_networking.bmEthernetStatistics;

			class_descriptor->descriptor.ethernet_networking.iMACAddress = usbd_alloc_string (class_description->description.ethernet_networking.iMACAddress);

			class_descriptor->descriptor.ethernet_networking.wMaxSegmentSize = cpu_to_le16 (class_description->description.ethernet_networking.wMaxSegmentSize);

			class_descriptor->descriptor.ethernet_networking.wNumberMCFilters = cpu_to_le16 (class_description->description.ethernet_networking.wNumberMCFilters);

			class_descriptor->descriptor.ethernet_networking.bNumberPowerFilters = class_description->description.ethernet_networking.bNumberPowerFilters;

			break;

		case USB_ST_ATMNF:
			break;

		case USB_ST_MDLM:
			dbg_init (1, "USB_ST_MDLM:");
			class_descriptor->descriptor.mobile_direct.bcdVersion = class_description->description.mobile_direct.bcdVersion;

			memcpy (class_descriptor->descriptor.mobile_direct.bGUID, class_description->description.mobile_direct.bGUID, sizeof (class_descriptor->descriptor.mobile_direct.bGUID));

			break;

		case USB_ST_MDLMD:

			dbg_init (1, "USB_ST_MDLMD: sizeof descriptor: %d %d length: %d elements: %d",
				  sizeof (struct usb_class_mdlmd_descriptor),
				  class_descriptor_sizes[USB_ST_MDLMD], class_descriptor->descriptor.mobile_direct_detail.bFunctionLength, class_description->elements);

			{
				unsigned char *cp;
				cp = (unsigned char *) class_descriptor;
				dbg_init (1, "descriptor: %02x %02x %02x %02x %02x %02x", cp[0], cp[1], cp[2], cp[3], cp[4], cp[5]);
			}
			class_descriptor->descriptor.mobile_direct_detail.bGuidDescriptorType = class_description->description.mobile_direct_detail.bGuidDescriptorType;

			//memcpy(class_descriptor->descriptor.mobile_direct_detail.bDetailData, 
			//        class_description->description.mobile_direct_detail.bDetailData,
			//        class_description->elements
			//        );

			class_descriptor->descriptor.mobile_direct_detail.bDetailData[0] = class_description->description.mobile_direct_detail.bDetailData[0];

			class_descriptor->descriptor.mobile_direct_detail.bDetailData[1] = class_description->description.mobile_direct_detail.bDetailData[1];

			dbg_init (1, "USB_ST_MDLMD: detaildata[0] %02x %02x",
				  class_descriptor->descriptor.mobile_direct_detail.bDetailData[0], class_description->description.mobile_direct_detail.bDetailData[0]
			    );

			dbg_init (1, "USB_ST_MDLMD: detaildata[1] %02x %02x",
				  class_descriptor->descriptor.mobile_direct_detail.bDetailData[1], class_description->description.mobile_direct_detail.bDetailData[1]
			    );

			{
				unsigned char *cp;
				cp = (unsigned char *) class_descriptor;
				dbg_init (1, "descriptor: %02x %02x %02x %02x %02x %02x", cp[0], cp[1], cp[2], cp[3], cp[4], cp[5]);
			}
			break;
		}

		// save in class descriptor array
		classes_descriptor_array[class] = class_descriptor;
	}
	return classes_descriptor_array;
}


/* *
 * alloc_function_endpoints - allocate endpoint descriptor array
 * @endpoints: number of endpoints
 * @endpoint_description_array: pointer to an array of endpoint descriptions
 *
 * Return a pointer to an array of pointers to endpoint descriptors. The descriptors
 * will be filled in with information from the endpoint description array.
 * 
 * Returning NULL will cause the caller to cleanup all previously allocated memory.
 */
__devinit static struct usb_endpoint_descriptor **alloc_function_endpoints (int endpoints, struct usb_endpoint_description *endpoint_description_array)
{
	int i;
	struct usb_endpoint_descriptor **endpoint_descriptor_array;


	if (!endpoints || !endpoint_description_array) {
		return NULL;
	}
	// allocate the endpoint descriptor array
	if (!(endpoint_descriptor_array = ckmalloc (sizeof (struct usb_endpoint_descriptor *) * endpoints, GFP_KERNEL))) {
		return NULL;
	}
	for (i = 0; i < endpoints; i++) {
		struct usb_endpoint_description *endpoint_description = endpoint_description_array + i;
		struct usb_endpoint_descriptor *endpoint_descriptor;

		dbg_init (1, "endpoint: %d:%d", i, endpoints);

		// allocate an endpoint descriptor to use
		if (!(endpoint_descriptor = usbd_alloc_descriptor (USB_DT_ENDPOINT, 0, 0))) {
			return NULL;
		}
		endpoint_descriptor->bEndpointAddress = endpoint_description->bEndpointAddress | endpoint_description->direction;
		endpoint_descriptor->bmAttributes = endpoint_description->bmAttributes;
		// XXX cpu_to_le16()?
		endpoint_descriptor->wMaxPacketSize =
			cpu_to_le16(endpoint_description->wMaxPacketSize);
		endpoint_descriptor->bInterval = endpoint_description->bInterval;

		// save in endpoint descriptor array
		endpoint_descriptor_array[i] = endpoint_descriptor;
	}
	return endpoint_descriptor_array;
}

__devinit static struct usb_class_report_descriptor **
alloc_function_reports(int classes, struct usb_class_description *class_description_array)
{
    int class;
    struct usb_class_report_descriptor **reports_descriptor_array;

    dbg_init(1,"classes: %d", classes);

    if (!classes || !class_description_array) {
        return NULL;
    }

    // allocate the class descriptor array
    if (!(reports_descriptor_array = ckmalloc(sizeof(struct usb_class_report_descriptor *)*classes, GFP_KERNEL))) {
        return NULL;
    }
    for (class = 0; class < classes; class++) {
        struct usb_class_description *class_description = class_description_array + class;
        struct usb_class_report_descriptor *report_descriptor;
        //int elements;

	if (class_description->bDescriptorType != USB_DT_HID)
		continue;
        dbg_init(1,"class: %d Type: %d DescriptionType: %d length: %d", 
                class, class_description->bDescriptorType,
		class_description->description.hid.bDescriptorType,
		class_description->description.hid.wDescriptorLength);

        // allocate an class descriptor to use
        if (!(report_descriptor = usbd_alloc_descriptor(class_description->description.hid.bDescriptorType, 0,
				class_description->description.hid.wDescriptorLength
							))) 
        {
            dbg_init(0, "usbd_alloc_descriptor failed");
            return NULL;
        }

	memcpy(&report_descriptor->bData[0],
	       class_description->description.hid.reportDescriptor,
	       class_description->description.hid.wDescriptorLength);
        report_descriptor->wLength =
		cpu_to_le16(class_description->description.hid.wDescriptorLength);

        // save in class descriptor array
        reports_descriptor_array[class] = report_descriptor;
    }
    return reports_descriptor_array;
}


/* *
 * alloc_endpoint_transfer - allocate endpoint transfer array
 * @endpoints: number of endpoints
 * @endpoint_description_array: pointer to an array of endpoint descriptions
 *
 * Return a pointer to an array of transfers sizes for each endpointi.
 * 
 * Returning NULL will cause the caller to cleanup all previously allocated memory.
 */
__devinit static int *alloc_endpoint_transfer (int endpoints, struct usb_endpoint_description *endpoint_description_array)
{
	int i;
	int *endpoint_transfersize;

	if (!endpoints || !endpoint_description_array) {
		return NULL;
	}
	// allocate the endpoint descriptor array
	if (!(endpoint_transfersize = ckmalloc (sizeof (int) * endpoints, GFP_KERNEL))) {
		return NULL;
	}
	for (i = 0; i < endpoints; i++) {
		struct usb_endpoint_description *endpoint_description = endpoint_description_array + i;
		dbg_init (1, "endpoint: %d:%d", i, endpoints);
		endpoint_transfersize[i] = endpoint_description->transferSize;
	}
	return endpoint_transfersize;
}

#if 0
/* *
 * alloc_function_interface - allocate alternate instance array
 *
 * Return a pointer to an array of alternate instances. Each instance contains a pointer
 * to a filled in alternate descriptor and a pointer to the endpoint descriptor array.
 *
 * Returning NULL will cause the caller to cleanup all previously allocated memory.
 */
__devinit static struct usb_alternate_instance *alloc_function_interface (int bInterfaceNumber, struct usb_interface_description *interface_description)
{
	struct usb_alternate_instance *alternate_instance;
	struct usb_interface_descriptor *interface_descriptor;

	dbg_init (1, "interface: %d", bInterfaceNumber);

	// allocate array of alternate instances
	if (!(alternate_instance = ckmalloc (sizeof (struct usb_alternate_instance), GFP_KERNEL))) {
		dbg_init (0, "ckmalloc failed");
		return NULL;
	}
	// allocate a descriptor for this interface
	if (!(interface_descriptor = usbd_alloc_descriptor (USB_DT_INTERFACE, 0, 0))) {
		dbg_init (0, "usbd_alloc_descriptor failed");
		return NULL;
	}

	interface_descriptor->bInterfaceNumber = bInterfaceNumber + 1;	// XXX
	interface_descriptor->bInterfaceClass = interface_description->bInterfaceClass;

	interface_descriptor->bInterfaceSubClass = interface_description->bInterfaceSubClass;
	interface_descriptor->bInterfaceProtocol = interface_description->bInterfaceProtocol;

	interface_descriptor->bAlternateSetting = 0;
	interface_descriptor->iInterface = usbd_alloc_string (interface_description->iInterface);

	// XXX it should be possible to collapse the next two functions into one.
	alternate_instance->endpoints_descriptor_array = NULL;
	alternate_instance->endpoint_transfersize_array = NULL;

	// save number of alternates, classes and endpoints for this alternate
	alternate_instance->endpoints = 0;
	alternate_instance->interface_descriptor = interface_descriptor;
	return alternate_instance;
}
#endif

/* *
 * alloc_function_alternates - allocate alternate instance array
 * @alternates: number of alternates
 * @alternate_description_array: pointer to an array of alternate descriptions
 *
 * Return a pointer to an array of alternate instances. Each instance contains a pointer
 * to a filled in alternate descriptor and a pointer to the endpoint descriptor array.
 *
 * Returning NULL will cause the caller to cleanup all previously allocated memory.
 */
__devinit static struct usb_alternate_instance *alloc_function_alternates (int bInterfaceNumber,
									   struct usb_interface_description *interface_description,
									   int alternates, struct usb_alternate_description *alternate_description_array)
{
	int i;
	struct usb_alternate_instance *alternate_instance_array;

	dbg_init (1, "bInterfaceNumber: %d, alternates: %d", bInterfaceNumber, alternates);

	// allocate array of alternate instances
	if (!(alternate_instance_array = ckmalloc (sizeof (struct usb_alternate_instance) * alternates, GFP_KERNEL))) {
		return NULL;
	}
	// iterate across the alternate descriptions
	for (i = 0; i < alternates; i++) {

		struct usb_alternate_description *alternate_description = alternate_description_array + i;
		struct usb_alternate_instance *alternate_instance = alternate_instance_array + i;
		struct usb_interface_descriptor *interface_descriptor;

		dbg_init (1, "alternate: %d:%d", i, alternates);

		// allocate a descriptor for this interface
		if (!(interface_descriptor = usbd_alloc_descriptor (USB_DT_INTERFACE, 0, 0))) {
			return NULL;
		}

		interface_descriptor->bInterfaceNumber = bInterfaceNumber;	// XXX
		interface_descriptor->bInterfaceClass = interface_description->bInterfaceClass;

		interface_descriptor->bInterfaceSubClass = interface_description->bInterfaceSubClass;
		interface_descriptor->bInterfaceProtocol = interface_description->bInterfaceProtocol;

		interface_descriptor->bAlternateSetting = alternate_description->bAlternateSetting;
		interface_descriptor->bNumEndpoints = alternate_description->endpoints;
		interface_descriptor->iInterface = usbd_alloc_string (alternate_description->iInterface ? alternate_description->iInterface : interface_description->iInterface);

		// XXX Classes

		alternate_instance->classes_descriptor_array =
			alloc_function_classes(
				alternate_description->classes,
				alternate_description->class_list);
		alternate_instance->reports_descriptor_array =
			alloc_function_reports(
				alternate_description->classes,
				alternate_description->class_list);

		alternate_instance->classes = alternate_description->classes;

		// XXX it should be possible to collapse the next two functions into one.
		alternate_instance->endpoints_descriptor_array = alloc_function_endpoints (alternate_description->endpoints, alternate_description->endpoint_list);

		alternate_instance->endpoint_transfersize_array = alloc_endpoint_transfer (alternate_description->endpoints, alternate_description->endpoint_list);


		// save number of alternates, classes and endpoints for this alternate
		alternate_instance->endpoints = alternate_description->endpoints;
		alternate_instance->interface_descriptor = interface_descriptor;
	}
	return alternate_instance_array;
}

/* *
 * alloc_function_interfaces - allocate interface instance array
 * @interfaces: number of interfaces
 * @interface_description_array: pointer to an array of interface descriptions
 *
 * Return a pointer to an array of interface instances. Each instance contains a pointer
 * to a filled in interface descriptor and a pointer to the endpoint descriptor array.
 *
 * Returning NULL will cause the caller to cleanup all previously allocated memory.
 */
__devinit static struct usb_interface_instance *alloc_function_interfaces (int interfaces, struct usb_interface_description *interface_description_array)
{
	int interface;
	struct usb_interface_instance *interface_instance_array;

	// allocate array of interface instances
	if (!(interface_instance_array = ckmalloc (sizeof (struct usb_interface_instance) * interfaces, GFP_KERNEL))) {
		return NULL;
	}
	// iterate across the interface descriptions
	for (interface = 0; interface < interfaces; interface++) {

		struct usb_interface_description *interface_description = interface_description_array + interface;
		struct usb_interface_instance *interface_instance = interface_instance_array + interface;

		dbg_init (1, "_");
		dbg_init (1, "interface: %d:%d   alternates: %d", interface, interfaces, interface_description->alternates);

		// save the interface description in the interface instance array
		//interface_instance_array[interface].interface_descriptor = interface_descriptor;

		interface_instance->alternates_instance_array = alloc_function_alternates (interface, interface_description, interface_description->alternates, interface_description->alternate_list);
		interface_instance->alternates = interface_description->alternates;

	}
	return interface_instance_array;
}


/* *
 * alloc_function_configurations - allocate configuration instance array
 * @configurations: number of configurations
 * @configuration_description_array: pointer to an array of configuration descriptions
 *
 * Return a pointer to an array of configuration instances. Each instance contains a pointer
 * to a filled in configuration descriptor and a pointer to the interface instances array.
 *
 * Returning NULL will cause the caller to cleanup all previously allocated memory.
 */
__devinit static struct usb_configuration_instance *alloc_function_configurations (int configurations, struct usb_configuration_description *configuration_description_array)
{
	int i;
	struct usb_configuration_instance *configuration_instance_array;


	// allocate array
	if (!(configuration_instance_array = ckmalloc (sizeof (struct usb_configuration_instance) * configurations, GFP_KERNEL))) {
		return NULL;
	}
	// fill in array
	for (i = 0; i < configurations; i++) {
		int j;
		int length;

		struct usb_configuration_description *configuration_description = configuration_description_array + i;
		struct usb_configuration_descriptor *configuration_descriptor;

		dbg_init (1, "configurations: %d:%d", i, configurations);

		// allocate a descriptor
		if (!(configuration_descriptor = usbd_alloc_descriptor (USB_DT_CONFIG, 0, 0))) {
			return NULL;
		}
		// setup fields in configuration descriptor
		// XXX c.f. 9.4.7 zero is default, so config MUST BE 1 to n, not 0 to n-1
		// XXX N.B. the configuration itself is fetched 0 to n-1.
		//
		configuration_descriptor->bConfigurationValue = i + 1;
		configuration_descriptor->wTotalLength = 0;
		configuration_descriptor->bNumInterfaces = configuration_description->interfaces;
		configuration_descriptor->iConfiguration = usbd_alloc_string (configuration_description->iConfiguration);
		configuration_descriptor->bmAttributes = configuration_description->bmAttributes;
		configuration_descriptor->bMaxPower = configuration_description->bMaxPower;

		// save the configuration descriptor in the configuration instance array
		configuration_instance_array[i].configuration_descriptor = configuration_descriptor;

		if (!(configuration_instance_array[i].interface_instance_array = alloc_function_interfaces (configuration_description->interfaces, configuration_description->interface_list))
		    ) {
			// XXX
			return NULL;
		}

		for (length = 0, j = 0; j < configuration_descriptor->bNumInterfaces; j++) {
			int alternate;
			struct usb_interface_instance *interface_instance = configuration_instance_array[i].interface_instance_array + j;


			for (alternate = 0; alternate < interface_instance->alternates; alternate++) {
				int class;
				struct usb_alternate_instance *alternate_instance = interface_instance->alternates_instance_array + alternate;

				for (class = 0; class < alternate_instance->classes; class++) {
					struct usb_class_descriptor *class_descriptor = alternate_instance->classes_descriptor_array[class];

					length += class_descriptor->descriptor.generic.bFunctionLength;
				}

				length += sizeof (struct usb_interface_descriptor) + alternate_instance->endpoints * sizeof (struct usb_endpoint_descriptor);
			}
		}
		configuration_descriptor->wTotalLength = cpu_to_le16 (sizeof (struct usb_configuration_descriptor) + length);

		dbg_init (1, "-------> [%d] %d %d %d length: %d", i,
			  configuration_descriptor->bNumInterfaces, configuration_descriptor->bConfigurationValue, configuration_descriptor->iConfiguration, length);
		configuration_instance_array[i].interfaces = configuration_description->interfaces;

	}
	return configuration_instance_array;
}


/* *
 * usbd_dealloc_function - deallocate a function driver
 * @function_driver: pointer to a function driver structure
 *
 * Find and free all descriptor structures associated with a function structure.
 */
static void __devexit usbd_dealloc_function (struct usb_function_driver *function_driver)
{
	int configuration;
	struct usb_configuration_instance *configuration_instance_array = function_driver->configuration_instance_array;

	dbg_init (1, "%s", function_driver->name);

	// iterate across the descriptors list and de-allocate all structures
	if (function_driver->configuration_instance_array) {
		for (configuration = 0; configuration < function_driver->configurations; configuration++) {
			int interface;
			struct usb_configuration_instance *configuration_instance = configuration_instance_array + configuration;

			dbg_init (1, "%s configuration: %d", function_driver->name, configuration);

			for (interface = 0; interface < configuration_instance->interfaces; interface++) {

				int alternate;
				struct usb_interface_instance *interface_instance = configuration_instance->interface_instance_array + interface;

				dbg_init (1, "%s configuration: %d interface: %d", function_driver->name, configuration, interface);

				for (alternate = 0; alternate < interface_instance->alternates; alternate++) {
					int class;
					int endpoint;
					struct usb_alternate_instance *alternate_instance = interface_instance->alternates_instance_array + alternate;

					dbg_init (1, "%s configuration: %d interface: %d alternate: %d", function_driver->name, configuration, interface, alternate);

					for (class = 0; class < alternate_instance->classes; class++) {

						dbg_init (1, "%s configuration: %d interface: %d alternate: %d class: %d", function_driver->name, configuration, interface, alternate, class);

						usbd_dealloc_descriptor ((struct usb_descriptor *)
									 alternate_instance->classes_descriptor_array[class]);
					}
					for (endpoint = 0; endpoint < alternate_instance->endpoints; endpoint++) {

						dbg_init (1, "%s configuration: %d interface: %d alternate: %d endpoint: %d", function_driver->name, configuration, interface, alternate, endpoint);

						usbd_dealloc_descriptor ((struct usb_descriptor *)
									 alternate_instance->endpoints_descriptor_array[endpoint]);
					}
					lkfree (alternate_instance->endpoints_descriptor_array);
					lkfree (alternate_instance->endpoint_transfersize_array);
					lkfree (alternate_instance->classes_descriptor_array);
					usbd_dealloc_descriptor ((struct usb_descriptor *) alternate_instance->interface_descriptor);
				}

				lkfree (interface_instance->alternates_instance_array);
			}

			lkfree (configuration_instance->interface_instance_array);
			usbd_dealloc_descriptor ((struct usb_descriptor *)
						 configuration_instance_array[configuration].configuration_descriptor);
		}
	}
	usbd_dealloc_descriptor ((struct usb_descriptor *) function_driver->device_descriptor);
	lkfree (configuration_instance_array);
}

__devinit static int usbd_strings_init (void)
{
	if (maxstrings > 254) {
		dbg_init (1, "setting maxstrings to maximum value of 254");
		maxstrings = 254;
	}
	if (!(usb_strings = ckmalloc (sizeof (struct usb_string_descriptor *) * maxstrings, GFP_KERNEL))) {
		return -1;
	}
	if (usbd_alloc_string_zero (LANGIDs) != 0) {
		lkfree (usb_strings);
		return -1;
	}
	return 0;
}



/* *
 * usbd_register_function - register a usb function driver
 * @function_driver: pointer to function driver structure
 *
 * Used by a USB Function driver to register itself with the usb device layer.
 *
 * It will create a usb_function_instance structure.
 *
 * The user friendly configuration/interface/endpoint descriptions are compiled into
 * the equivalent ready to use descriptor records.
 *
 * All function drivers must register before any bus interface drivers.
 *
 */
EXPORT_SYMBOL(usbd_register_function);
__devinit int usbd_register_function (struct usb_function_driver *function_driver)
{
	//struct usb_device_descriptor *device_descriptor;

	/* Create a function driver structure, copy the configuration,
	 * interface and endpoint descriptions into descriptors, add to the
	 * function drivers list.
	 */

	dbg_init (1, "--");

	if (registered_devices) {
		dbg_init (1, "cannot register after device created: %s", function_driver->name);
		return -EINVAL;
	}
	// initialize the strings pool
	if (usbd_strings_init ()) {
		dbg_init (0, "usbd_strings_init failed");
		return -EINVAL;
	}


	dbg_init (9, "USE_COUNT %d", GET_USE_COUNT (THIS_MODULE));
	list_add_tail (&function_driver->drivers, &function_drivers);
	registered_functions++;
	dbg_init (1, "registered_functions: %d", registered_functions);
	dbg_init (1, "--------------");

	return 0;
}


/* *
 * usbd_deregister_function - called by a USB FUNCTION driver to deregister itself
 * @function_driver: pointer to struct usb_function_driver
 *
 * Called by a USB Function driver De-register a usb function driver.
 */
EXPORT_SYMBOL(usbd_deregister_function);
void __devexit usbd_deregister_function (struct usb_function_driver *function_driver)
{
	dbg_init (1, "%s", function_driver->name);

	list_del (&function_driver->drivers);
	registered_functions--;

	//usbd_dealloc_function(function_driver);

	dbg_init (9, "USE_COUNT %d", GET_USE_COUNT (THIS_MODULE));
}




void usbd_function_init (struct usb_bus_instance *bus, struct usb_device_instance *device, struct usb_function_driver *function_driver)
{
	struct usb_device_descriptor *device_descriptor;

	dbg_init (1, "-");

	if (function_driver->ops->function_init) {
		function_driver->ops->function_init (bus, device, function_driver);
	}

	if (!(device_descriptor = usbd_alloc_descriptor (USB_DT_DEVICE, 0, 0))) {
		return;
	}

	device_descriptor->bcdUSB = cpu_to_le16(USB_BCD_VERSION);
	device_descriptor->bDeviceClass = function_driver->device_description->bDeviceClass;
	device_descriptor->bDeviceSubClass = function_driver->device_description->bDeviceSubClass;
	device_descriptor->bDeviceProtocol = function_driver->device_description->bDeviceProtocol;

	//device_descriptor->bMaxPacketSize0 = 0;             // this will be set in ep0
	// XXX test that this works, should be able to remove from ep0
	device_descriptor->bMaxPacketSize0 = bus->driver->maxpacketsize;

	device_descriptor->idVendor =
		cpu_to_le16(function_driver->device_description->idVendor);
	device_descriptor->idProduct =
		cpu_to_le16(function_driver->device_description->idProduct);

	device_descriptor->bcdDevice = 0;	// XXX

	device_descriptor->iManufacturer = usbd_alloc_string (function_driver->device_description->iManufacturer);
	device_descriptor->iProduct = usbd_alloc_string (function_driver->device_description->iProduct);

	dbg_init (1, "*      *       *       *");
#ifdef CONFIG_USBD_SERIAL_NUMBER_STR
	dbg_init (1, "SERIAL_NUMBER");
	if (bus->serial_number_str && strlen (bus->serial_number_str)) {
		dbg_init (0, "bus->serial_number_str: %s", bus->serial_number_str);
		device_descriptor->iSerialNumber = usbd_alloc_string (bus->serial_number_str);
	} else {
		dbg_init (0, "function_driver->device_description->iSerialNumber: %s", function_driver->device_description->iSerialNumber);
		device_descriptor->iSerialNumber = usbd_alloc_string (function_driver->device_description->iSerialNumber);
	}
	dbg_init (1, "device_descriptor->iSerialNumber: %d", device_descriptor->iSerialNumber);
#else
	// XXX should not need to do anything here
#endif
	device_descriptor->bNumConfigurations = function_driver->configurations;

	dbg_init (3, "configurations: %d", device_descriptor->bNumConfigurations);



	// allocate the configuration descriptor array
	if (!(function_driver->configuration_instance_array = alloc_function_configurations (function_driver->configurations, function_driver->configuration_description))) {
		dbg_init (1, "failed during function configuration %s", function_driver->name);
		usbd_dealloc_descriptor ((struct usb_descriptor *) device_descriptor);
		return;
	}

	function_driver->device_descriptor = device_descriptor;

	dbg_init (1, "- finished");
}

void usbd_function_close (struct usb_device_instance *device)
{
	struct usb_function_instance *function;

	if ((function = usbd_device_function_instance (device, 0))) {
		if (function->function_driver->ops->function_exit) {
			function->function_driver->ops->function_exit (device);
		}
	}
	usbd_dealloc_function (function->function_driver);
}