/*
 * Copyright 2005-2008, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Stephan Aßmus <superstippi@gmx.de>
 *
 * Portions of this code are based on Be Sample Code released under the
 * Be Sample Code license. (USB sound device driver sample code IIRC.)
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#include <Drivers.h>
#include <KernelExport.h>
#include <OS.h>
#include <USB3.h>
 
int32 api_version = B_CUR_DRIVER_API_VERSION;
 
#define DEBUG_DRIVER 0
 
#if DEBUG_DRIVER
#	define DPRINTF_INFO(x) dprintf x;
#	define DPRINTF_ERR(x) dprintf x;
#else
#	define DPRINTF_INFO(x)
#	define DPRINTF_ERR(x) dprintf x;
#endif
 
typedef struct wacom_device wacom_device;
 
struct wacom_device {
	wacom_device*		next;
 
	int					open;
	int					number;
 
	usb_device			dev;
 
	usb_pipe			pipe;
	char*				data;
	size_t				max_packet_size;
	size_t				length;
 
	uint16				vendor;
	uint16				product;
 
	sem_id				notify_lock;
	uint32				status;
};
 
// handy strings for referring to ourself
#define ID "wacom: "
static const char* kDriverName = "wacom";
static const char* kBasePublishPath = "input/wacom/usb/";
 
// list of device instances and names for publishing
static wacom_device* sDeviceList = NULL;
static sem_id sDeviceListLock = -1;
static int sDeviceCount = 0;
 
static char** sDeviceNames = NULL;
 
// handle for the USB bus manager
static usb_module_info* usb;
 
// These rather inelegant routines are used to assign numbers to
// device instances so that they have unique names in devfs.
 
static uint32 sDeviceNumbers = 0;
 
// get_number
static int
get_number()
{
	int num;
 
	for (num = 0; num < 32; num++) {
		if (!(sDeviceNumbers & (1 << num))) {
			sDeviceNumbers |= (1 << num);
			return num;
		}
	}
 
	return -1;
}
 
// put_number
static void
put_number(int num)
{
	sDeviceNumbers &= ~(1 << num);
}
 
// #pragma mark - Device addition and removal
//
// add_device() and remove_device() are used to create and tear down
// device instances.  They are used from the callbacks device_added()
// and device_removed() which are invoked by the USB bus manager.
 
// add_device
static wacom_device*
add_device(usb_device dev)
{
	wacom_device *device = NULL;
	int num, ifc, alt;
	const usb_interface_info *ii;
	status_t st;
	const usb_device_descriptor* udd;
	const usb_configuration_info *conf;
	bool setConfiguration = false;
 
	// we need these four for a Wacom tablet
	size_t controlTransferLength;
	int tryCount;
	char repData[2] = { 0x02, 0x02 };
	char retData[2] = { 0x00, 0x00 };
 
	conf = usb->get_configuration(dev);
	DPRINTF_INFO((ID "add_device(%ld, %p)\n", dev, conf));
 
	if ((num = get_number()) < 0)
		return NULL;
 
	udd = usb->get_device_descriptor(dev);
	// only pick up wacom tablets
	if (udd && udd->vendor_id == 0x056a) {
 
		DPRINTF_ERR((ID "add_device() - wacom detected\n"));
 
		// see if the device has been configured already
		if (!conf) {
			conf = usb->get_nth_configuration(dev, 0);
			setConfiguration = true;
		}
 
		if (!conf)
			goto fail;
 
		for (ifc = 0; ifc < conf->interface_count; ifc++) {
			DPRINTF_INFO((ID "add_device() - examining interface: %d\n", ifc));
			for (alt = 0; alt < conf->interface[ifc].alt_count; alt++) {
				ii = &conf->interface[ifc].alt[alt];
				DPRINTF_INFO((ID "add_device() - examining alt interface: "
					"%d\n", alt));
 
 
				// does it have the correct type of interface?
				if (ii->descr->interface_class != 3) continue;
				if (ii->descr->interface_subclass != 1) continue;
				if (ii->endpoint_count != 1) continue;
 
				// only accept input endpoints
				if (ii->endpoint[0].descr->endpoint_address & 0x80) {
					DPRINTF_INFO((ID "add_device() - found input endpoint\n"));
					goto got_one;
				}
			}
		}
	} else
		goto fail;
 
fail:
	put_number(num);
	if (device) {
		free(device->data);
		free(device);
	}
	return NULL;
 
got_one:
	if ((device = (wacom_device *) malloc(sizeof(wacom_device))) == NULL)
		goto fail;
 
	device->dev = dev;
	device->number = num;
	device->open = 1;
	device->notify_lock = -1;
	device->data = NULL;
 
//	if (setConfiguration) {
		// the configuration has to be set yet (was not the current one)
		DPRINTF_INFO((ID "add_device() - setting configuration...\n"));
		if ((st = usb->set_configuration(dev, conf)) != B_OK) {
			dprintf(ID "add_device() -> set_configuration() returns %ld\n", st);
			goto fail;
		} else
			DPRINTF_ERR((ID " ... success!\n"));
 
		if (conf->interface[ifc].active != ii) {
			// the interface we found is not the active one and has to be set
			DPRINTF_INFO((ID "add_device() - setting interface: %p...\n", ii));
			if ((st = usb->set_alt_interface(dev, ii)) != B_OK) {
				dprintf(ID "add_device() -> set_alt_interface() returns %ld\n",
					st);
				goto fail;
			} else
				DPRINTF_ERR((ID " ... success!\n"));
		}
		// see if the device is a Wacom tablet and needs some special treatment
		// let's hope Wacom doesn't produce normal mice any time soon, or this
		// check will have to be more specific about product_id...hehe
		if (udd->vendor_id == 0x056a) {
			// do the control transfers to set up absolute mode (default is HID
			// mode)
 
			// see 'Device Class Definition for HID 1.11' (HID1_11.pdf),
			// par. 7.2 (available at www.usb.org/developers/hidpage)
 
			// set protocol mode to 'report' (instead of 'boot')
			controlTransferLength = 0;
			// HID Class-Specific Request, Host to device (=0x21):
			// SET_PROTOCOL (=0x0b) to Report Protocol (=1)
			// of Interface #0 (=0)
			st = usb->send_request(dev, 0x21, 0x0b, 1, 0, 0, 0,
				&controlTransferLength);
 
			if (st < B_OK) {
				dprintf(ID "add_device() - control transfer 1 failed: %ld\n",
					st);
			}
 
			// try up to five times to set the tablet to 'Wacom'-mode (enabling
			// absolute mode, pressure data, etc.)
			controlTransferLength = 2;
 
			for (tryCount = 0; tryCount < 5; tryCount++) {
				// HID Class-Specific Request, Host to device (=0x21):
				// SET_REPORT (=0x09) type Feature (=3) with ID 2 (=2) of
				// Interface #0 (=0) to repData (== { 0x02, 0x02 })
				st = usb->send_request(dev, 0x21, 0x09, (3 << 8) + 2, 0, 2,
					repData, &controlTransferLength);
 
				if (st < B_OK) {
					dprintf(ID "add_device() - control transfer 2 failed: %ld"
						"\n", st);
				}
 
				// check if registers are set correctly
 
				// HID Class-Specific Request, Device to host (=0xA1):
				// GET_REPORT (=0x01) type Feature (=3) with ID 2 (=2) of
				// Interface #0 (=0) to retData
				st = usb->send_request(dev, 0xA1, 0x01, (3 << 8) + 2, 0, 2,
					retData, &controlTransferLength);
 
				if (st < B_OK) {
					dprintf(ID "add_device() - control transfer 3 failed: %ld"
						"\n", st);
				}
 
				DPRINTF_INFO((ID "add_device() - retData: %u - %u\n",
					retData[0], retData[1]));
 
				if (retData[0] == repData[0] && retData[1] == repData[1]) {
					DPRINTF_INFO((ID "add_device() - successfully set "
						"'Wacom'-mode\n"));
					break;
				}
			}
 
			DPRINTF_INFO((ID "add_device() - number of tries: %u\n",
				tryCount + 1));
 
			if (tryCount > 4) {
				dprintf(ID "add_device() - set 'Wacom'-mode failed\n");
			}
		}
//	}
 
	// configure the rest of the wacom_device
	device->pipe = ii->endpoint[0].handle;
//DPRINTF_INFO((ID "add_device() - pipe id = %ld\n", device->pipe));
	device->length = 0;
	device->max_packet_size = ii->endpoint[0].descr->max_packet_size;
	device->data = (char*)malloc(device->max_packet_size);
	if (device->data == NULL)
		goto fail;
//DPRINTF_INFO((ID "add_device() - max packet length = %ld\n",
//	device->max_packet_size));
	device->status = 0;//B_USB_STATUS_SUCCESS;
	device->vendor = udd->vendor_id;
	device->product = udd->product_id;
 
	DPRINTF_INFO((ID "add_device() - added %p (/dev/%s%d)\n", device,
		kBasePublishPath, num));
 
	// add it to the list of devices so it will be published, etc
	acquire_sem(sDeviceListLock);
	device->next = sDeviceList;
	sDeviceList = device;
	sDeviceCount++;
	release_sem(sDeviceListLock);
 
	return device;
}
 
// remove_device
static void
remove_device(wacom_device *device)
{
	put_number(device->number);
 
	usb->cancel_queued_transfers(device->pipe);
 
	delete_sem(device->notify_lock);
	if (device->data)
		free(device->data);
	free(device);
}
 
// device_added
static status_t
device_added(usb_device dev, void** cookie)
{
	wacom_device* device;
 
	DPRINTF_INFO((ID "device_added(%ld,...)\n", dev));
 
	// first see, if this device is already added
	acquire_sem(sDeviceListLock);
	for (device = sDeviceList; device; device = device->next) {
		DPRINTF_ERR((ID "device_added() - old device: %ld\n", device->dev));
		if (device->dev == dev) {
			DPRINTF_ERR((ID "device_added() - already added - done!\n"));
			*cookie = (void*)device;
			release_sem(sDeviceListLock);
			return B_OK;
		}
	}
	release_sem(sDeviceListLock);
 
	if ((device = add_device(dev)) != NULL) {
		*cookie = (void*)device;
		DPRINTF_INFO((ID "device_added() - done!\n"));
		return B_OK;
	} else
		DPRINTF_INFO((ID "device_added() - failed to add device!\n"));
 
	return B_ERROR;
}
 
// device_removed
static status_t
device_removed(void *cookie)
{
	wacom_device *device = (wacom_device *) cookie;
 
	DPRINTF_INFO((ID "device_removed(%p)\n", device));
 
	if (device) {
 
		acquire_sem(sDeviceListLock);
 
		// remove it from the list of devices
		if (device == sDeviceList) {
			sDeviceList = device->next;
		} else {
			wacom_device *n;
			for (n = sDeviceList; n; n = n->next) {
				if (n->next == device) {
					n->next = device->next;
					break;
				}
			}
		}
		sDeviceCount--;
 
		// tear it down if it's not open --
		// otherwise the last device_free() will handle it
 
		device->open--;
 
		DPRINTF_ERR((ID "device_removed() open: %d\n", device->open));
 
		if (device->open == 0) {
			remove_device(device);
		} else {
			dprintf(ID "device /dev/%s%d still open -- marked for removal\n",
				kBasePublishPath, device->number);
		}
 
		release_sem(sDeviceListLock);
	}
 
	return B_OK;
}
 
// #pragma mark - Device Hooks
//
// Here we implement the posixy driver hooks (open/close/read/write/ioctl)
 
// device_open
static status_t
device_open(const char *dname, uint32 flags, void** cookie)
{
	wacom_device *device;
	int n;
	status_t ret = B_ERROR;
 
	char controlDevicePath[1024];
	sprintf(controlDevicePath, "%s%s", kBasePublishPath, "control");
	if (strcmp(dname, controlDevicePath) == 0) {
		dprintf(ID "device_open() -> refuse to open control device\n");
		return B_ERROR;
	}
 
	n = atoi(dname + strlen(kBasePublishPath));
 
	DPRINTF_INFO((ID "device_open(\"%s\",%d,...)\n", dname, flags));
 
	acquire_sem(sDeviceListLock);
	for (device = sDeviceList; device; device = device->next) {
		if (device->number == n) {
//			if (device->open <= 1) {
				device->open++;
				*cookie = device;
				DPRINTF_ERR((ID "device_open() open: %d\n", device->open));
 
				if (device->notify_lock < 0) {
					device->notify_lock = create_sem(0, "notify_lock");
					if (device->notify_lock < 0) {
						ret = device->notify_lock;
						device->open--;
						*cookie = NULL;
						dprintf(ID "device_open() -> create_sem() returns %ld\n",
							ret);
					} else {
						ret = B_OK;
					}
				}
				release_sem(sDeviceListLock);
				return ret;
//			} else {
//				dprintf(ID "device_open() -> device is already open %ld\n", ret);
//				release_sem(sDeviceListLock);
//				return B_ERROR;
//			}
		}
	}
	release_sem(sDeviceListLock);
	return ret;
}
 
// device_close
static status_t
device_close (void *cookie)
{
#if DEBUG_DRIVER
	wacom_device *device = (wacom_device*) cookie;
	DPRINTF_ERR((ID "device_close() name = \"%s%d\"\n", kBasePublishPath,
		device->number));
#endif
	return B_OK;
}
 
// device_free
static status_t
device_free(void *cookie)
{
	wacom_device *device = (wacom_device *)cookie;
 
	DPRINTF_INFO((ID "device_free() name = \"%s%d\"\n", kBasePublishPath,
		device->number));
 
	acquire_sem(sDeviceListLock);
 
	device->open--;
 
	DPRINTF_INFO((ID "device_free() open: %ld\n", device->open));
 
	if (device->open == 0) {
		remove_device(device);
	}
	release_sem(sDeviceListLock);
 
	return B_OK;
}
 
// device_interupt_callback
static void
device_interupt_callback(void* cookie, status_t status, void* data,
	uint32 actualLength)
{
	wacom_device* device = (wacom_device*)cookie;
	uint32 length = min_c(actualLength, device->max_packet_size);
 
	DPRINTF_INFO((ID "device_interupt_callback(%p) name = \"%s%d\" -> "
		"status: %ld, length: %ld\n", cookie, kBasePublishPath, device->number,
		status, actualLength));
 
	device->status = status;
	if (device->notify_lock >= 0) {
		if (status == B_OK) {
			memcpy(device->data, data, length);
			device->length = length;
		} else {
			device->length = 0;
		}
		release_sem(device->notify_lock);
	}
 
	DPRINTF_INFO((ID "device_interupt_callback() - done\n"));
}
 
// read_header
static void
read_header(const wacom_device* device, void* buffer)
{
	uint16* ids = (uint16*)buffer;
	uint32* size = (uint32*)buffer;
 
	ids[0] = device->vendor;
	ids[1] = device->product;
	size[1] = device->max_packet_size;
}
 
// device_read
static status_t
device_read(void* cookie, off_t pos, void* buf, size_t* count)
{
	wacom_device* device = (wacom_device*) cookie;
	status_t ret = B_BAD_VALUE;
	uint8* buffer = (uint8*)buf;
	uint32 dataLength;
 
	if (!device)
		return ret;
 
	ret = device->notify_lock;
 
	DPRINTF_INFO((ID "device_read(%p,%Ld,0x%x,%d) name = \"%s%d\"\n",
			 cookie, pos, buf, *count, kBasePublishPath, device->number));
 
	if (ret >= B_OK) {
		// what the client "reads" is decided depending on how much bytes are
		// provided 8 bytes are needed to "read" vendor id, product id and max
		// packet size in case the client wants to read more than 8 bytes, a usb
		// interupt transfer is scheduled, and an error report is returned as
		// appropriate
		if (*count > 8) {
			// queue the interrupt transfer
			ret = usb->queue_interrupt(device->pipe, device->data,
				device->max_packet_size, device_interupt_callback, device);
			if (ret >= B_OK) {
				// we will block here until the interrupt transfer has been done
				ret = acquire_sem_etc(device->notify_lock, 1,
					B_RELATIVE_TIMEOUT, 500 * 1000);
				// handle time out
				if (ret < B_OK) {
					usb->cancel_queued_transfers(device->pipe);
					acquire_sem(device->notify_lock);
						// collect the sem released by the cancel
 
					if (ret == B_TIMED_OUT) {
						// a time_out is ok, since it only means that the device
						// had nothing to report (ie mouse/pen was not moved)
						// within the given time interval
						DPRINTF_INFO((ID "device_read(%p) name = \"%s%d\" -> "
							"B_TIMED_OUT\n", cookie, kBasePublishPath,
							device->number));
						*count = 8;
						read_header(device, buffer);
						ret = B_OK;
					} else {
						// any other error trying to acquire the semaphore
						*count = 0;
					}
				} else {
					if (device->status == 0/*B_USBD_SUCCESS*/) {
						DPRINTF_INFO((ID "interrupt transfer - success\n"));
						// copy the data from the buffer
						dataLength = min_c(device->length, *count - 8);
						*count = dataLength + 8;
						read_header(device, buffer);
						memcpy(buffer + 8, device->data, dataLength);
					} else {
						// an error happened during the interrupt transfer
						*count = 0;
						dprintf(ID "interrupt transfer - failure: %ld\n",
							device->status);
						ret = B_ERROR;
					}
				}
			} else {
				*count = 0;
				dprintf(ID "device_read(%p) name = \"%s%d\" -> error queuing "
					"interrupt: %ld\n", cookie, kBasePublishPath,
					device->number, ret);
			}
		} else if (*count == 8) {
			read_header(device, buffer);
			ret = B_OK;
		} else {
			dprintf(ID "device_read(%p) name = \"%s%d\" -> buffer size must be "
				"at least 8 bytes!\n", cookie, kBasePublishPath,
				device->number);
			*count = 0;
			ret = B_BAD_VALUE;
		}
	}
 
	return ret;
}
 
// device_write
static status_t
device_write(void *cookie, off_t pos, const void *buf, size_t *count)
{
	return B_ERROR;
}
 
// device_control
static status_t
device_control (void *cookie, uint32 msg, void *arg1, size_t len)
{
	return B_ERROR;
}
 
// #pragma mark - Driver Hooks
//
// These functions provide the glue used by DevFS to load/unload
// the driver and also handle registering with the USB bus manager
// to receive device added and removed events
 
static usb_notify_hooks notify_hooks =
{
	&device_added,
	&device_removed
};
 
static const usb_support_descriptor kSupportedDevices[] =
{
	{ 3, 1, 2, 0, 0 }
};
 
// init_hardware
status_t
init_hardware(void)
{
	return B_OK;
}
 
// init_driver
status_t
init_driver(void)
{
	DPRINTF_INFO((ID "init_driver(), built %s %s\n", __DATE__, __TIME__));
 
#if DEBUG_DRIVER && !defined(__HAIKU__)
	if (load_driver_symbols(kDriverName) == B_OK) {
		DPRINTF_INFO((ID "loaded symbols\n"));
	} else {
		DPRINTF_ERR((ID "no driver symbols loaded!\n"));
	}
#endif
 
	if (get_module(B_USB_MODULE_NAME, (module_info**) &usb) != B_OK) {
		DPRINTF_ERR((ID "cannot get module \"%s\"\n", B_USB_MODULE_NAME));
		return B_ERROR;
	}
 
	if ((sDeviceListLock = create_sem(1,"sDeviceListLock")) < 0) {
		put_module(B_USB_MODULE_NAME);
		return sDeviceListLock;
	}
 
	usb->register_driver(kDriverName, kSupportedDevices, 1, NULL);
	usb->install_notify(kDriverName, &notify_hooks);
 
	return B_OK;
}
 
// uninit_driver
void
uninit_driver(void)
{
	int i;
 
	DPRINTF_INFO((ID "uninit_driver()\n"));
 
	usb->uninstall_notify(kDriverName);
 
	delete_sem(sDeviceListLock);
 
	put_module(B_USB_MODULE_NAME);
 
	if (sDeviceNames) {
		for (i = 0; sDeviceNames[i]; i++)
			free(sDeviceNames[i]);
		free(sDeviceNames);
	}
 
	DPRINTF_INFO((ID "uninit_driver() done\n"));
}
 
// publish_devices
const char**
publish_devices()
{
	wacom_device *device;
	int i;
 
	DPRINTF_INFO((ID "publish_devices()\n"));
 
	if (sDeviceNames) {
		for (i = 0; sDeviceNames[i]; i++)
			free((char *) sDeviceNames[i]);
		free(sDeviceNames);
	}
 
	acquire_sem(sDeviceListLock);
	sDeviceNames = (char**)malloc(sizeof(char*) * (sDeviceCount + 2));
	if (sDeviceNames) {
		for (i = 0, device = sDeviceList; device; device = device->next) {
			sDeviceNames[i] = (char*)malloc(strlen(kBasePublishPath) + 4);
			if (sDeviceNames[i]) {
				sprintf(sDeviceNames[i],"%s%d",kBasePublishPath,device->number);
				DPRINTF_INFO((ID "publishing: \"/dev/%s\"\n",sDeviceNames[i]));
				i++;
			}
		}
		// publish the currently fake control device
		sDeviceNames[i] = (char*)malloc(strlen(kBasePublishPath) + 8);
		if (sDeviceNames[i])
			sprintf(sDeviceNames[i], "%s%s", kBasePublishPath, "control");
 
		sDeviceNames[i + 1] = NULL;
	}
 
	release_sem(sDeviceListLock);
 
	return (const char**)sDeviceNames;
}
 
static device_hooks sDeviceHooks = {
	device_open,
	device_close,
	device_free,
	device_control,
	device_read,
	device_write
};
 
// find_device
device_hooks*
find_device(const char* name)
{
	return &sDeviceHooks;
}

V576 Incorrect format. Consider checking the fifth actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.