/* * Copyright 2005-2020, 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 #include #include 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; size_t ifc, alt; const usb_interface_info *ii; status_t st; const usb_device_descriptor* udd; const usb_configuration_info *conf; // 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); } 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; DPRINTF_INFO((ID "add_device() - setting configuration...\n")); if ((st = usb->set_configuration(dev, conf)) != B_OK) { dprintf(ID "add_device() -> " "set_configuration() returns %" B_PRId32 "\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 %" B_PRId32 "\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: %" B_PRId32 "\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: %" B_PRId32 "\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: %" B_PRId32 "\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: %" B_PRIu32 "\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; 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 %" B_PRId32 "\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_interrupt_callback static void device_interrupt_callback(void* cookie, status_t status, void* data, size_t actualLength) { wacom_device* device = (wacom_device*)cookie; size_t length = min_c(actualLength, device->max_packet_size); DPRINTF_INFO((ID "device_interrupt_callback(%p) name = \"%s%d\" -> " "status: %ld, length: %zu\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_interrupt_callback() - done\n")); } // read_header static status_t read_header(const wacom_device* device, void* buffer) { wacom_device_header device_header; device_header.vendor_id = device->vendor; device_header.product_id = device->product; device_header.max_packet_size = device->max_packet_size; if (!IS_USER_ADDRESS(buffer)) { memcpy(buffer, &device_header, sizeof(wacom_device_header)); return B_OK; } return user_memcpy(buffer, &device_header, sizeof(wacom_device_header)); } // 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,%lld,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. "sizeof(wacom_device_header)" bytes are needed to "read" // vendor id, product id and max packet size in case the client wants to // read more than "sizeof(wacom_device_header)" bytes, a usb interupt // transfer is scheduled, and an error report is returned as appropriate if (*count > sizeof(wacom_device_header)) { // queue the interrupt transfer ret = usb->queue_interrupt(device->pipe, device->data, device->max_packet_size, device_interrupt_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 = sizeof(wacom_device_header); ret = read_header(device, buffer); } 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 - sizeof(wacom_device_header)); *count = dataLength + sizeof(wacom_device_header); ret = read_header(device, buffer); if (ret == B_OK) { if (IS_USER_ADDRESS(buffer)) ret = user_memcpy( buffer + sizeof(wacom_device_header), device->data, dataLength); else memcpy(buffer + sizeof(wacom_device_header), device->data, dataLength); } } else { // an error happened during the interrupt transfer *count = 0; dprintf(ID "interrupt transfer - " "failure: %" B_PRIu32 "\n", device->status); ret = B_ERROR; } } } else { *count = 0; dprintf(ID "device_read(%p) name = \"%s%d\" -> error queuing " "interrupt: %" B_PRId32 "\n", cookie, kBasePublishPath, device->number, ret); } } else if (*count == sizeof(wacom_device_header)) { ret = read_header(device, buffer); } else { dprintf(ID "device_read(%p) name = \"%s%d\" -> buffer size must be " "at least the size of the wacom_device_header struct!\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, ¬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 *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++; } } sDeviceNames[i] = 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; }