diff --git a/src/add-ons/kernel/drivers/input/Jamfile b/src/add-ons/kernel/drivers/input/Jamfile index 193122137b..cb1ad3accb 100644 --- a/src/add-ons/kernel/drivers/input/Jamfile +++ b/src/add-ons/kernel/drivers/input/Jamfile @@ -2,3 +2,4 @@ SubDir HAIKU_TOP src add-ons kernel drivers input ; SubInclude HAIKU_TOP src add-ons kernel drivers input ps2_hid ; SubInclude HAIKU_TOP src add-ons kernel drivers input usb_hid ; +SubInclude HAIKU_TOP src add-ons kernel drivers input wacom ; diff --git a/src/add-ons/kernel/drivers/input/wacom/Jamfile b/src/add-ons/kernel/drivers/input/wacom/Jamfile new file mode 100644 index 0000000000..a48f8482ad --- /dev/null +++ b/src/add-ons/kernel/drivers/input/wacom/Jamfile @@ -0,0 +1,16 @@ +SubDir HAIKU_TOP src add-ons kernel drivers input wacom ; + +SetSubDirSupportedPlatformsBeOSCompatible ; + +SubDirSysHdrs $(HAIKU_TOP) headers os drivers ; + +KernelAddon wacom : + wacom.c +; + +Package haiku-inputkit-cvs : + wacpm : + boot home config add-ons kernel drivers bin ; + +PackageDriverSymLink haiku-inputkit-cvs : input wacom ; + diff --git a/src/add-ons/kernel/drivers/input/wacom/wacom.c b/src/add-ons/kernel/drivers/input/wacom/wacom.c new file mode 100644 index 0000000000..5cba358c6d --- /dev/null +++ b/src/add-ons/kernel/drivers/input/wacom/wacom.c @@ -0,0 +1,684 @@ +/* + * Copyright 2005-2008, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + * + * 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 +#include +#include + +#include +#include +#include +#include + + +#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 driver by 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 *md = 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 two for a Wacom tablet + size_t controlTransferLength; + char repData[2] = { 0x02, 0x02 }; + + 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 (md) { + if (md->data) + free(md->data); + free(md); + } + return NULL; + +got_one: + if ((md = (wacom_device *) malloc(sizeof(wacom_device))) == NULL) + goto fail; + + md->dev = dev; + md->number = num; + md->open = 1; + md->notify_lock = -1; + md->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) + controlTransferLength = 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); + + // "set interface" -> ?!? + controlTransferLength = 2; + st = usb->send_request(dev, 0x21, 0x09, (3 << 8) + 2, 1, 2, repData, + &controlTransferLength); + + if (st < B_OK) + dprintf(ID "add_device() - control transfer 2 failed: %ld\n", st); + } + } + + // configure the rest of the wacom_device + md->pipe = ii->endpoint[0].handle; +//DPRINTF_INFO((ID "add_device() - pipe id = %ld\n", md->pipe)); + md->length = 0; + md->max_packet_size = ii->endpoint[0].descr->max_packet_size; + md->data = (char*)malloc(md->max_packet_size); + if (md->data == NULL) + goto fail; +//DPRINTF_INFO((ID "add_device() - max packet length = %ld\n", md->max_packet_size)); + md->status = 0;//B_USB_STATUS_SUCCESS; + md->vendor = udd->vendor_id; + md->product = udd->product_id; + + DPRINTF_INFO((ID "add_device() - added %p (/dev/%s%d)\n", md, kBasePublishPath, num)); + + // add it to the list of devices so it will be published, etc + acquire_sem(sDeviceListLock); + md->next = sDeviceList; + sDeviceList = md; + sDeviceCount++; + release_sem(sDeviceListLock); + + return md; +} + +// remove_device +static void +remove_device(wacom_device *md) +{ + put_number(md->number); + + usb->cancel_queued_transfers(md->pipe); + + delete_sem(md->notify_lock); + if (md->data) + free(md->data); + free(md); +} + +// device_added +static status_t +device_added(usb_device dev, void** cookie) +{ + wacom_device* md; + + DPRINTF_INFO((ID "device_added(%ld,...)\n", dev)); + + // first see, if this device is already added + acquire_sem(sDeviceListLock); + for (md = sDeviceList; md; md = md->next) { + DPRINTF_ERR((ID "device_added() - old device: %ld\n", md->dev)); + if (md->dev == dev) { + DPRINTF_ERR((ID "device_added() - already added - done!\n")); + *cookie = (void*)md; + release_sem(sDeviceListLock); + return B_OK; + } + } + release_sem(sDeviceListLock); + + if ((md = add_device(dev)) != NULL) { + *cookie = (void*)md; + DPRINTF_INFO((ID "device_added() - done!\n")); + return B_OK; + } else + DPRINTF_ERR((ID "device_added() - failed to add device!\n")); + + return B_ERROR; +} + +// device_removed +static status_t +device_removed(void *cookie) +{ + wacom_device *md = (wacom_device *) cookie; + + DPRINTF_INFO((ID "device_removed(%p)\n", md)); + + if (md) { + + acquire_sem(sDeviceListLock); + + // remove it from the list of devices + if (md == sDeviceList) { + sDeviceList = md->next; + } else { + wacom_device *n; + for (n = sDeviceList; n; n = n->next) { + if (n->next == md) { + n->next = md->next; + break; + } + } + } + sDeviceCount--; + + // tear it down if it's not open -- + // otherwise the last device_free() will handle it + + md->open--; + + DPRINTF_ERR((ID "device_removed() open: %d\n", md->open)); + + if (md->open == 0) { + remove_device(md); + } else { + dprintf(ID "device /dev/%s%d still open -- marked for removal\n", + kBasePublishPath, md->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 *md; + 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 (md = sDeviceList; md; md = md->next) { + if (md->number == n) { +// if (md->open <= 1) { + md->open++; + *cookie = md; + DPRINTF_ERR((ID "device_open() open: %d\n", md->open)); + + if (md->notify_lock < 0) { + if ((md->notify_lock = create_sem(0, "notify_lock")) < 0) { + ret = md->notify_lock; + md->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 *md = (wacom_device*) cookie; + DPRINTF_ERR((ID "device_close() name = \"%s%d\"\n", kBasePublishPath, md->number)); +#endif + return B_OK; +} + +// device_free +static status_t +device_free(void *cookie) +{ + wacom_device *md = (wacom_device *)cookie; + + DPRINTF_INFO((ID "device_free() name = \"%s%d\"\n", kBasePublishPath, md->number)); + + acquire_sem(sDeviceListLock); + + md->open--; + + DPRINTF_INFO((ID "device_free() open: %ld\n", md->open)); + + if (md->open == 0) { + remove_device(md); + } + release_sem(sDeviceListLock); + + return B_OK; +} + +// device_interupt_callback +static void +device_interupt_callback(void* cookie, uint32 status, + void* data, uint32 actual_len) +{ + wacom_device* md = (wacom_device*)cookie; + uint32 length = min_c(actual_len, md->max_packet_size); + + DPRINTF_INFO((ID "device_interupt_callback(%p) name = \"%s%d\" -> " + "status: %ld, length: %ld\n", cookie, kBasePublishPath, md->number, + status, actual_len)); + + md->status = status; + if (md->notify_lock >= 0) { + if (status == 0/*B_USB_STATUS_SUCCESS*/) { + memcpy(md->data, data, length); + md->length = length; + } else { + md->length = 0; + } + release_sem(md->notify_lock); + } + + DPRINTF_INFO((ID "device_interupt_callback() - done\n")); +} + +// read_header +static void +read_header(const wacom_device* md, void* buffer) +{ + uint16* ids = (uint16*)buffer; + uint32* size = (uint32*)buffer; + + ids[0] = md->vendor; + ids[1] = md->product; + size[1] = md->max_packet_size; +} + +// device_read +static status_t +device_read(void* cookie, off_t pos, void* buf, size_t* count) +{ + wacom_device* md = (wacom_device*) cookie; + status_t ret = B_BAD_VALUE; + uint8* buffer = (uint8*)buf; + uint32 dataLength; + + if (!md) + return ret; + + ret = md->notify_lock; + + DPRINTF_INFO((ID "device_read(%p,%Ld,0x%x,%d) name = \"%s%d\"\n", + cookie, pos, buf, *count, kBasePublishPath, md->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(md->pipe, md->data, md->max_packet_size, + device_interupt_callback, md); + if (ret >= B_OK) { + // we will block here until the interrupt transfer has been done + ret = acquire_sem_etc(md->notify_lock, 1, B_RELATIVE_TIMEOUT, 500 * 1000); + // handle time out + if (ret < B_OK) { +// usb->cancel_queued_transfers(md->pipe); + 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, md->number)); + *count = 8; + read_header(md, buffer); + ret = B_OK; + } else { + // any other error trying to acquire the semaphore + *count = 0; + } + } else { + if (md->status == 0/*B_USBD_SUCCESS*/) { + DPRINTF_INFO((ID "interrupt transfer - success\n")); + // copy the data from the buffer + dataLength = min_c(md->length, *count - 8); + *count = dataLength + 8; + read_header(md, buffer); + memcpy(buffer + 8, md->data, dataLength); + } else { + // an error happened during the interrupt transfer + *count = 0; + dprintf(ID "interrupt transfer - failure: %ld\n", md->status); + ret = B_ERROR; + } + } + } else { + *count = 0; + dprintf(ID "device_read(%p) name = \"%s%d\" -> error queuing interrupt: %ld\n", + cookie, kBasePublishPath, md->number, ret); + } + } else if (*count == 8) { + read_header(md, buffer); + ret = B_OK; + } else { + dprintf(ID "device_read(%p) name = \"%s%d\" -> buffer size must be at least 8 bytes!\n", + cookie, kBasePublishPath, md->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 + 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, ¬ify_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 *md; + 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, md = sDeviceList; md; md = md->next) { + if ((sDeviceNames[i] = (char *) malloc(strlen(kBasePublishPath) + 4))) { + sprintf(sDeviceNames[i],"%s%d",kBasePublishPath,md->number); + DPRINTF_INFO((ID "publishing: \"/dev/%s\"\n",sDeviceNames[i])); + i++; + } + } + // publish the currently fake control device + if ((sDeviceNames[i] = (char *) malloc(strlen(kBasePublishPath) + 8))) { + 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; +}