* added a kernel driver for Wacom USB tablets.

* the driver should be generic for all Wacom tablets, but there is a
  special control transfer to put the devices in to "absolute mode", it
  may not work with future tablets. The driver allows to use read devices,
  for which there is a special protocol. If the read request has a certain
  size, then the driver will transmit info on the device, like the vendor
  and product id as well as the max the packet size, for reads larger than
  that, it will issue an interrupt transfer and put the raw data received
  from the device behind the info header


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@23666 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Stephan Aßmus
2008-01-20 19:08:52 +00:00
parent 701f410777
commit 7fd8d11d3a
3 changed files with 701 additions and 0 deletions
+1
View File
@@ -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 ps2_hid ;
SubInclude HAIKU_TOP src add-ons kernel drivers input usb_hid ; SubInclude HAIKU_TOP src add-ons kernel drivers input usb_hid ;
SubInclude HAIKU_TOP src add-ons kernel drivers input wacom ;
@@ -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 ;
@@ -0,0 +1,684 @@
/*
* Copyright 2005-2008, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <[email protected]>
*
* 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>
#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, &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 *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;
}