/*
 * usb.c
 *
 * The usb functions
 *
 * Copyright (C) 2001 Greg Kroah-Hartman <greg@kroah.com>
 *
 *	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 version 2 of the License.
 * 
 *	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 <stddef.h>	/* for NULL */
#include <stdlib.h>	/* for getenv() */
#include <errno.h>
#include "hotplug.h"

#include "usb_modules.h"

/* bitmap values taken from include/linux/usb.h */
#define USB_DEVICE_ID_MATCH_VENDOR		0x0001
#define USB_DEVICE_ID_MATCH_PRODUCT		0x0002
#define USB_DEVICE_ID_MATCH_DEV_LO		0x0004
#define USB_DEVICE_ID_MATCH_DEV_HI		0x0008
#define USB_DEVICE_ID_MATCH_DEV_CLASS		0x0010
#define USB_DEVICE_ID_MATCH_DEV_SUBCLASS	0x0020
#define USB_DEVICE_ID_MATCH_DEV_PROTOCOL	0x0040
#define USB_DEVICE_ID_MATCH_INT_CLASS		0x0080
#define USB_DEVICE_ID_MATCH_INT_SUBCLASS	0x0100
#define USB_DEVICE_ID_MATCH_INT_PROTOCOL	0x0200
#define USB_DEVICE_ID_MATCH_DEVICE		(USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT)
#define USB_DEVICE_ID_MATCH_DEV_RANGE		(USB_DEVICE_ID_MATCH_DEV_LO | USB_DEVICE_ID_MATCH_DEV_HI)
#define USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION	(USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_DEV_RANGE)
#define USB_DEVICE_ID_MATCH_DEV_INFO		(USB_DEVICE_ID_MATCH_DEV_CLASS | USB_DEVICE_ID_MATCH_DEV_SUBCLASS | USB_DEVICE_ID_MATCH_DEV_PROTOCOL)
#define USB_DEVICE_ID_MATCH_INT_INFO		(USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS | USB_DEVICE_ID_MATCH_INT_PROTOCOL)
	


static int match_vendor_product (unsigned short vendor, unsigned short product, unsigned short bcdDevice)
{
	int i;
	int retval;

	dbg ("vendor = %x, product = %x, bcdDevice = %x", vendor, product, bcdDevice);

	for (i = 0; usb_module_map[i].module_name != NULL; ++i) {
		dbg ("looking at %s, match_flags = %x", usb_module_map[i].module_name, usb_module_map[i].match_flags);
		if (usb_module_map[i].match_flags & (USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_DEV_RANGE)) {
			if ((usb_module_map[i].match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
			    (usb_module_map[i].idVendor != vendor)) {
				dbg ("vendor check failed %x != %x", usb_module_map[i].idVendor, vendor);
				continue;
			}
			if ((usb_module_map[i].match_flags & USB_DEVICE_ID_MATCH_PRODUCT) &&
			    (usb_module_map[i].idProduct != product)) {
				dbg ("product check failed %x != %x", usb_module_map[i].idProduct, product);
				continue;
			}
			if ((usb_module_map[i].match_flags & USB_DEVICE_ID_MATCH_DEV_LO) &&
			    (usb_module_map[i].bcdDevice_lo < bcdDevice)) {
				dbg ("bcdDevice_lo check failed %x > %x", usb_module_map[i].bcdDevice_lo, bcdDevice);
				continue;
			}
			if ((usb_module_map[i].match_flags & USB_DEVICE_ID_MATCH_DEV_HI) &&
			    (usb_module_map[i].bcdDevice_hi >= bcdDevice)) {
				dbg ("bcdDevice_hi check failed %x <= %x", usb_module_map[i].bcdDevice_hi, bcdDevice);
				continue;
			}
			/* found one! */
			dbg ("loading %s", usb_module_map[i].module_name);
			retval = load_module (usb_module_map[i].module_name);
			if (retval)
				return retval;
		}
	}

	return -ENODEV;
}
	

static int match_device_class (unsigned char class, unsigned char subclass, unsigned char protocol)
{
	int i;
	int retval;

	dbg ("class = %x, subclass = %x, protocol = %x", class, subclass, protocol);

	for (i = 0; usb_module_map[i].module_name != NULL; ++i) {
		dbg ("looking at %s, match_flags = %x", usb_module_map[i].module_name, usb_module_map[i].match_flags);
		if (usb_module_map[i].match_flags & USB_DEVICE_ID_MATCH_DEV_INFO) {
			if ((usb_module_map[i].match_flags & USB_DEVICE_ID_MATCH_DEV_CLASS) &&
			    (usb_module_map[i].bDeviceClass != class)) {
				dbg ("class check failed %x != %x", usb_module_map[i].bDeviceClass, class);
				continue;
			}
			if ((usb_module_map[i].match_flags & USB_DEVICE_ID_MATCH_DEV_SUBCLASS) &&
			    (usb_module_map[i].bDeviceSubClass != subclass)) {
				dbg ("subclass check failed %x != %x", usb_module_map[i].bDeviceSubClass, subclass);
				continue;
			}
			if ((usb_module_map[i].match_flags & USB_DEVICE_ID_MATCH_DEV_PROTOCOL) &&
			    (usb_module_map[i].bDeviceProtocol != protocol)) {
				dbg ("protocol check failed %x != %x", usb_module_map[i].bDeviceProtocol, protocol);
				continue;
			}
			/* found one! */
			dbg ("loading %s", usb_module_map[i].module_name);
			retval = load_module (usb_module_map[i].module_name);
			if (retval)
				return retval;
		}
	}

	return -ENODEV;
}


static int match_interface_class (unsigned char class, unsigned char subclass, unsigned char protocol)
{
	int i;
	int retval;

	dbg ("class = %x, subclass = %x, protocol = %x", class, subclass, protocol);

	for (i = 0; usb_module_map[i].module_name != NULL; ++i) {
		dbg ("looking at %s, match_flags = %x", usb_module_map[i].module_name, usb_module_map[i].match_flags);
		if (usb_module_map[i].match_flags & USB_DEVICE_ID_MATCH_INT_INFO) {
			if ((usb_module_map[i].match_flags & USB_DEVICE_ID_MATCH_INT_CLASS) &&
			    (usb_module_map[i].bInterfaceClass != class)) {
				dbg ("class check failed %x != %x", usb_module_map[i].bInterfaceClass, class);
				continue;
			}
			if ((usb_module_map[i].match_flags & USB_DEVICE_ID_MATCH_INT_SUBCLASS) &&
			    (usb_module_map[i].bInterfaceSubClass != subclass)) {
				dbg ("subclass check failed %x != %x", usb_module_map[i].bInterfaceSubClass, subclass);
				continue;
			}
			if ((usb_module_map[i].match_flags & USB_DEVICE_ID_MATCH_INT_PROTOCOL) &&
			    (usb_module_map[i].bInterfaceProtocol != protocol)) {
				dbg ("protocol check failed %x != %x", usb_module_map[i].bInterfaceProtocol, protocol);
				continue;
			}
			/* found one! */
			dbg ("loading %s", usb_module_map[i].module_name);
			retval = load_module (usb_module_map[i].module_name);
			if (retval)
				return retval;
		}
	}

	return -ENODEV;
}


static int usb_add (void)
{
	char *product_env;
	char *type_env;
	char *interface_env;
	int error;
	unsigned int idVendor;
	unsigned int idProduct;
	unsigned int bcdDevice;
	unsigned int device_class;
	unsigned int device_subclass;
	unsigned int device_protocol;
	unsigned int interface_class = 0;
	unsigned int interface_subclass = 0;
	unsigned int interface_protocol = 0;
	
	product_env = getenv ("PRODUCT");
	type_env = getenv ("TYPE");
	if ((product_env == NULL) ||
	    (type_env == NULL)) {
		dbg ("missing an environment variable, aborting.");
		return 1;
	}
	error = split_3values (product_env, 16, &idVendor, &idProduct, &bcdDevice);
	if (error)
		return error;

	error = match_vendor_product (idVendor, idProduct, bcdDevice);
	if (error)
		return error;
	
	error = split_3values (type_env, 10, &device_class,
			       &device_subclass, &device_protocol);
	if (error)
		return error;
	error = match_device_class ((unsigned char)device_class,
				    (unsigned char)device_subclass,
				    (unsigned char)device_protocol);
	if (error)
		return error;

	/* we need to look at the interface too */
	interface_env = getenv ("INTERFACE");
	if (interface_env == NULL) {
		dbg ("interface is null, we don't know what to do here.");
		return 1;
	}
	error = split_3values (interface_env, 10, &interface_class,
			       &interface_subclass, &interface_protocol);
	if (error)
		return error;
	error = match_interface_class ((unsigned char)interface_class,
				       (unsigned char)interface_subclass,
				       (unsigned char)interface_protocol);
	return error;
}


static int usb_remove (void)
{
	/* right now we don't do anything here :) */
	return 0;
}


static struct subsystem usb_subsystem[] = {
	{ ADD_STRING, usb_add },
	{ REMOVE_STRING, usb_remove },
	{ NULL, NULL }
};


int usb_handler (void)
{
	char * action;
	
	action = getenv ("ACTION");
	dbg ("action = %s", action);
	if (action == NULL) {
		dbg ("missing ACTION environment variable, aborting.");
		return 1;
	}

	return call_subsystem (action, usb_subsystem);
}


