xref: /haiku/src/add-ons/kernel/drivers/input/wacom/wacom.c (revision 77fb9ca3e653f72d1d15c9f1a50c3d4287f680e0)
1 /*
2  * Copyright 2005-2008, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stephan Aßmus <superstippi@gmx.de>
7  *
8  * Portions of this code are based on Be Sample Code released under the
9  * Be Sample Code license. (USB sound device driver sample code IIRC.)
10  */
11 
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 
16 #include <Drivers.h>
17 #include <KernelExport.h>
18 #include <OS.h>
19 #include <USB3.h>
20 
21 int32 api_version = B_CUR_DRIVER_API_VERSION;
22 
23 #define DEBUG_DRIVER 0
24 
25 #if DEBUG_DRIVER
26 #	define DPRINTF_INFO(x) dprintf x;
27 #	define DPRINTF_ERR(x) dprintf x;
28 #else
29 #	define DPRINTF_INFO(x)
30 #	define DPRINTF_ERR(x) dprintf x;
31 #endif
32 
33 typedef struct wacom_device wacom_device;
34 
35 struct wacom_device {
36 	wacom_device*		next;
37 
38 	int					open;
39 	int					number;
40 
41 	usb_device			dev;
42 
43 	usb_pipe			pipe;
44 	char*				data;
45 	size_t				max_packet_size;
46 	size_t				length;
47 
48 	uint16				vendor;
49 	uint16				product;
50 
51 	sem_id				notify_lock;
52 	uint32				status;
53 };
54 
55 // handy strings for referring to ourself
56 #define ID "wacom: "
57 static const char* kDriverName = "wacom";
58 static const char* kBasePublishPath = "input/wacom/usb/";
59 
60 // list of device instances and names for publishing
61 static wacom_device* sDeviceList = NULL;
62 static sem_id sDeviceListLock = -1;
63 static int sDeviceCount = 0;
64 
65 static char** sDeviceNames = NULL;
66 
67 // handle for the USB bus manager
68 static usb_module_info* usb;
69 
70 // These rather inelegant routines are used to assign numbers to
71 // device instances so that they have unique names in devfs.
72 
73 static uint32 sDeviceNumbers = 0;
74 
75 // get_number
76 static int
77 get_number()
78 {
79 	int num;
80 
81 	for (num = 0; num < 32; num++) {
82 		if (!(sDeviceNumbers & (1 << num))) {
83 			sDeviceNumbers |= (1 << num);
84 			return num;
85 		}
86 	}
87 
88 	return -1;
89 }
90 
91 // put_number
92 static void
93 put_number(int num)
94 {
95 	sDeviceNumbers &= ~(1 << num);
96 }
97 
98 // #pragma mark - Device addition and removal
99 //
100 // add_device() and remove_device() are used to create and tear down
101 // device instances.  They are used from the callbacks device_added()
102 // and device_removed() which are invoked by the USB bus manager.
103 
104 // add_device
105 static wacom_device*
106 add_device(usb_device dev)
107 {
108 	wacom_device *device = NULL;
109 	int num, ifc, alt;
110 	const usb_interface_info *ii;
111 	status_t st;
112 	const usb_device_descriptor* udd;
113 	const usb_configuration_info *conf;
114 	bool setConfiguration = false;
115 
116 	// we need these four for a Wacom tablet
117 	size_t controlTransferLength;
118 	int tryCount;
119 	char repData[2] = { 0x02, 0x02 };
120 	char retData[2] = { 0x00, 0x00 };
121 
122 	conf = usb->get_configuration(dev);
123 	DPRINTF_INFO((ID "add_device(%ld, %p)\n", dev, conf));
124 
125 	if ((num = get_number()) < 0)
126 		return NULL;
127 
128 	udd = usb->get_device_descriptor(dev);
129 	// only pick up wacom tablets
130 	if (udd && udd->vendor_id == 0x056a) {
131 
132 		DPRINTF_ERR((ID "add_device() - wacom detected\n"));
133 
134 		// see if the device has been configured already
135 		if (!conf) {
136 			conf = usb->get_nth_configuration(dev, 0);
137 			setConfiguration = true;
138 		}
139 
140 		if (!conf)
141 			goto fail;
142 
143 		for (ifc = 0; ifc < conf->interface_count; ifc++) {
144 			DPRINTF_INFO((ID "add_device() - examining interface: %d\n", ifc));
145 			for (alt = 0; alt < conf->interface[ifc].alt_count; alt++) {
146 				ii = &conf->interface[ifc].alt[alt];
147 				DPRINTF_INFO((ID "add_device() - examining alt interface: "
148 					"%d\n", alt));
149 
150 
151 				// does it have the correct type of interface?
152 				if (ii->descr->interface_class != 3) continue;
153 				if (ii->descr->interface_subclass != 1) continue;
154 				if (ii->endpoint_count != 1) continue;
155 
156 				// only accept input endpoints
157 				if (ii->endpoint[0].descr->endpoint_address & 0x80) {
158 					DPRINTF_INFO((ID "add_device() - found input endpoint\n"));
159 					goto got_one;
160 				}
161 			}
162 		}
163 	} else
164 		goto fail;
165 
166 fail:
167 	put_number(num);
168 	if (device) {
169 		free(device->data);
170 		free(device);
171 	}
172 	return NULL;
173 
174 got_one:
175 	if ((device = (wacom_device *) malloc(sizeof(wacom_device))) == NULL)
176 		goto fail;
177 
178 	device->dev = dev;
179 	device->number = num;
180 	device->open = 1;
181 	device->notify_lock = -1;
182 	device->data = NULL;
183 
184 //	if (setConfiguration) {
185 		// the configuration has to be set yet (was not the current one)
186 		DPRINTF_INFO((ID "add_device() - setting configuration...\n"));
187 		if ((st = usb->set_configuration(dev, conf)) != B_OK) {
188 			dprintf(ID "add_device() -> "
189 				"set_configuration() returns %" B_PRId32 "\n", st);
190 			goto fail;
191 		} else
192 			DPRINTF_ERR((ID " ... success!\n"));
193 
194 		if (conf->interface[ifc].active != ii) {
195 			// the interface we found is not the active one and has to be set
196 			DPRINTF_INFO((ID "add_device() - setting interface: %p...\n", ii));
197 			if ((st = usb->set_alt_interface(dev, ii)) != B_OK) {
198 				dprintf(ID "add_device() -> "
199 					"set_alt_interface() returns %" B_PRId32 "\n", st);
200 				goto fail;
201 			} else
202 				DPRINTF_ERR((ID " ... success!\n"));
203 		}
204 		// see if the device is a Wacom tablet and needs some special treatment
205 		// let's hope Wacom doesn't produce normal mice any time soon, or this
206 		// check will have to be more specific about product_id...hehe
207 		if (udd->vendor_id == 0x056a) {
208 			// do the control transfers to set up absolute mode (default is HID
209 			// mode)
210 
211 			// see 'Device Class Definition for HID 1.11' (HID1_11.pdf),
212 			// par. 7.2 (available at www.usb.org/developers/hidpage)
213 
214 			// set protocol mode to 'report' (instead of 'boot')
215 			controlTransferLength = 0;
216 			// HID Class-Specific Request, Host to device (=0x21):
217 			// SET_PROTOCOL (=0x0b) to Report Protocol (=1)
218 			// of Interface #0 (=0)
219 			st = usb->send_request(dev, 0x21, 0x0b, 1, 0, 0, 0,
220 				&controlTransferLength);
221 
222 			if (st < B_OK) {
223 				dprintf(ID "add_device() - "
224 					"control transfer 1 failed: %" B_PRId32 "\n", st);
225 			}
226 
227 			// try up to five times to set the tablet to 'Wacom'-mode (enabling
228 			// absolute mode, pressure data, etc.)
229 			controlTransferLength = 2;
230 
231 			for (tryCount = 0; tryCount < 5; tryCount++) {
232 				// HID Class-Specific Request, Host to device (=0x21):
233 				// SET_REPORT (=0x09) type Feature (=3) with ID 2 (=2) of
234 				// Interface #0 (=0) to repData (== { 0x02, 0x02 })
235 				st = usb->send_request(dev, 0x21, 0x09, (3 << 8) + 2, 0, 2,
236 					repData, &controlTransferLength);
237 
238 				if (st < B_OK) {
239 					dprintf(ID "add_device() - "
240 						"control transfer 2 failed: %" B_PRId32 "\n", st);
241 				}
242 
243 				// check if registers are set correctly
244 
245 				// HID Class-Specific Request, Device to host (=0xA1):
246 				// GET_REPORT (=0x01) type Feature (=3) with ID 2 (=2) of
247 				// Interface #0 (=0) to retData
248 				st = usb->send_request(dev, 0xA1, 0x01, (3 << 8) + 2, 0, 2,
249 					retData, &controlTransferLength);
250 
251 				if (st < B_OK) {
252 					dprintf(ID "add_device() - "
253 						"control transfer 3 failed: %" B_PRId32 "\n", st);
254 				}
255 
256 				DPRINTF_INFO((ID "add_device() - retData: %u - %u\n",
257 					retData[0], retData[1]));
258 
259 				if (retData[0] == repData[0] && retData[1] == repData[1]) {
260 					DPRINTF_INFO((ID "add_device() - successfully set "
261 						"'Wacom'-mode\n"));
262 					break;
263 				}
264 			}
265 
266 			DPRINTF_INFO((ID "add_device() - number of tries: %u\n",
267 				tryCount + 1));
268 
269 			if (tryCount > 4) {
270 				dprintf(ID "add_device() - set 'Wacom'-mode failed\n");
271 			}
272 		}
273 //	}
274 
275 	// configure the rest of the wacom_device
276 	device->pipe = ii->endpoint[0].handle;
277 //DPRINTF_INFO((ID "add_device() - pipe id = %ld\n", device->pipe));
278 	device->length = 0;
279 	device->max_packet_size = ii->endpoint[0].descr->max_packet_size;
280 	device->data = (char*)malloc(device->max_packet_size);
281 	if (device->data == NULL)
282 		goto fail;
283 //DPRINTF_INFO((ID "add_device() - max packet length = %ld\n",
284 //	device->max_packet_size));
285 	device->status = 0;//B_USB_STATUS_SUCCESS;
286 	device->vendor = udd->vendor_id;
287 	device->product = udd->product_id;
288 
289 	DPRINTF_INFO((ID "add_device() - added %p (/dev/%s%d)\n", device,
290 		kBasePublishPath, num));
291 
292 	// add it to the list of devices so it will be published, etc
293 	acquire_sem(sDeviceListLock);
294 	device->next = sDeviceList;
295 	sDeviceList = device;
296 	sDeviceCount++;
297 	release_sem(sDeviceListLock);
298 
299 	return device;
300 }
301 
302 // remove_device
303 static void
304 remove_device(wacom_device *device)
305 {
306 	put_number(device->number);
307 
308 	usb->cancel_queued_transfers(device->pipe);
309 
310 	delete_sem(device->notify_lock);
311 	if (device->data)
312 		free(device->data);
313 	free(device);
314 }
315 
316 // device_added
317 static status_t
318 device_added(usb_device dev, void** cookie)
319 {
320 	wacom_device* device;
321 
322 	DPRINTF_INFO((ID "device_added(%ld,...)\n", dev));
323 
324 	// first see, if this device is already added
325 	acquire_sem(sDeviceListLock);
326 	for (device = sDeviceList; device; device = device->next) {
327 		DPRINTF_ERR((ID "device_added() - old device: %" B_PRIu32 "\n",
328 			device->dev));
329 		if (device->dev == dev) {
330 			DPRINTF_ERR((ID "device_added() - already added - done!\n"));
331 			*cookie = (void*)device;
332 			release_sem(sDeviceListLock);
333 			return B_OK;
334 		}
335 	}
336 	release_sem(sDeviceListLock);
337 
338 	if ((device = add_device(dev)) != NULL) {
339 		*cookie = (void*)device;
340 		DPRINTF_INFO((ID "device_added() - done!\n"));
341 		return B_OK;
342 	} else
343 		DPRINTF_INFO((ID "device_added() - failed to add device!\n"));
344 
345 	return B_ERROR;
346 }
347 
348 // device_removed
349 static status_t
350 device_removed(void *cookie)
351 {
352 	wacom_device *device = (wacom_device *) cookie;
353 
354 	DPRINTF_INFO((ID "device_removed(%p)\n", device));
355 
356 	if (device) {
357 
358 		acquire_sem(sDeviceListLock);
359 
360 		// remove it from the list of devices
361 		if (device == sDeviceList) {
362 			sDeviceList = device->next;
363 		} else {
364 			wacom_device *n;
365 			for (n = sDeviceList; n; n = n->next) {
366 				if (n->next == device) {
367 					n->next = device->next;
368 					break;
369 				}
370 			}
371 		}
372 		sDeviceCount--;
373 
374 		// tear it down if it's not open --
375 		// otherwise the last device_free() will handle it
376 
377 		device->open--;
378 
379 		DPRINTF_ERR((ID "device_removed() open: %d\n", device->open));
380 
381 		if (device->open == 0) {
382 			remove_device(device);
383 		} else {
384 			dprintf(ID "device /dev/%s%d still open -- marked for removal\n",
385 				kBasePublishPath, device->number);
386 		}
387 
388 		release_sem(sDeviceListLock);
389 	}
390 
391 	return B_OK;
392 }
393 
394 // #pragma mark - Device Hooks
395 //
396 // Here we implement the posixy driver hooks (open/close/read/write/ioctl)
397 
398 // device_open
399 static status_t
400 device_open(const char *dname, uint32 flags, void** cookie)
401 {
402 	wacom_device *device;
403 	int n;
404 	status_t ret = B_ERROR;
405 
406 	char controlDevicePath[1024];
407 	sprintf(controlDevicePath, "%s%s", kBasePublishPath, "control");
408 	if (strcmp(dname, controlDevicePath) == 0) {
409 		dprintf(ID "device_open() -> refuse to open control device\n");
410 		return B_ERROR;
411 	}
412 
413 	n = atoi(dname + strlen(kBasePublishPath));
414 
415 	DPRINTF_INFO((ID "device_open(\"%s\",%d,...)\n", dname, flags));
416 
417 	acquire_sem(sDeviceListLock);
418 	for (device = sDeviceList; device; device = device->next) {
419 		if (device->number == n) {
420 //			if (device->open <= 1) {
421 				device->open++;
422 				*cookie = device;
423 				DPRINTF_ERR((ID "device_open() open: %d\n", device->open));
424 
425 				if (device->notify_lock < 0) {
426 					device->notify_lock = create_sem(0, "notify_lock");
427 					if (device->notify_lock < 0) {
428 						ret = device->notify_lock;
429 						device->open--;
430 						*cookie = NULL;
431 						dprintf(ID "device_open() -> "
432 							"create_sem() returns %" B_PRId32 "\n", ret);
433 					} else {
434 						ret = B_OK;
435 					}
436 				}
437 				release_sem(sDeviceListLock);
438 				return ret;
439 //			} else {
440 //				dprintf(ID "device_open() -> device is already open %ld\n", ret);
441 //				release_sem(sDeviceListLock);
442 //				return B_ERROR;
443 //			}
444 		}
445 	}
446 	release_sem(sDeviceListLock);
447 	return ret;
448 }
449 
450 // device_close
451 static status_t
452 device_close (void *cookie)
453 {
454 #if DEBUG_DRIVER
455 	wacom_device *device = (wacom_device*) cookie;
456 	DPRINTF_ERR((ID "device_close() name = \"%s%d\"\n", kBasePublishPath,
457 		device->number));
458 #endif
459 	return B_OK;
460 }
461 
462 // device_free
463 static status_t
464 device_free(void *cookie)
465 {
466 	wacom_device *device = (wacom_device *)cookie;
467 
468 	DPRINTF_INFO((ID "device_free() name = \"%s%d\"\n", kBasePublishPath,
469 		device->number));
470 
471 	acquire_sem(sDeviceListLock);
472 
473 	device->open--;
474 
475 	DPRINTF_INFO((ID "device_free() open: %ld\n", device->open));
476 
477 	if (device->open == 0) {
478 		remove_device(device);
479 	}
480 	release_sem(sDeviceListLock);
481 
482 	return B_OK;
483 }
484 
485 // device_interupt_callback
486 static void
487 device_interupt_callback(void* cookie, status_t status, void* data,
488 	uint32 actualLength)
489 {
490 	wacom_device* device = (wacom_device*)cookie;
491 	uint32 length = min_c(actualLength, device->max_packet_size);
492 
493 	DPRINTF_INFO((ID "device_interupt_callback(%p) name = \"%s%d\" -> "
494 		"status: %ld, length: %ld\n", cookie, kBasePublishPath, device->number,
495 		status, actualLength));
496 
497 	device->status = status;
498 	if (device->notify_lock >= 0) {
499 		if (status == B_OK) {
500 			memcpy(device->data, data, length);
501 			device->length = length;
502 		} else {
503 			device->length = 0;
504 		}
505 		release_sem(device->notify_lock);
506 	}
507 
508 	DPRINTF_INFO((ID "device_interupt_callback() - done\n"));
509 }
510 
511 // read_header
512 static void
513 read_header(const wacom_device* device, void* buffer)
514 {
515 	uint16* ids = (uint16*)buffer;
516 	uint32* size = (uint32*)buffer;
517 
518 	ids[0] = device->vendor;
519 	ids[1] = device->product;
520 	size[1] = device->max_packet_size;
521 }
522 
523 // device_read
524 static status_t
525 device_read(void* cookie, off_t pos, void* buf, size_t* count)
526 {
527 	wacom_device* device = (wacom_device*) cookie;
528 	status_t ret = B_BAD_VALUE;
529 	uint8* buffer = (uint8*)buf;
530 	uint32 dataLength;
531 
532 	if (!device)
533 		return ret;
534 
535 	ret = device->notify_lock;
536 
537 	DPRINTF_INFO((ID "device_read(%p,%Ld,0x%x,%d) name = \"%s%d\"\n",
538 			 cookie, pos, buf, *count, kBasePublishPath, device->number));
539 
540 	if (ret >= B_OK) {
541 		// what the client "reads" is decided depending on how much bytes are
542 		// provided 8 bytes are needed to "read" vendor id, product id and max
543 		// packet size in case the client wants to read more than 8 bytes, a usb
544 		// interupt transfer is scheduled, and an error report is returned as
545 		// appropriate
546 		if (*count > 8) {
547 			// queue the interrupt transfer
548 			ret = usb->queue_interrupt(device->pipe, device->data,
549 				device->max_packet_size, device_interupt_callback, device);
550 			if (ret >= B_OK) {
551 				// we will block here until the interrupt transfer has been done
552 				ret = acquire_sem_etc(device->notify_lock, 1,
553 					B_RELATIVE_TIMEOUT, 500 * 1000);
554 				// handle time out
555 				if (ret < B_OK) {
556 					usb->cancel_queued_transfers(device->pipe);
557 					acquire_sem(device->notify_lock);
558 						// collect the sem released by the cancel
559 
560 					if (ret == B_TIMED_OUT) {
561 						// a time_out is ok, since it only means that the device
562 						// had nothing to report (ie mouse/pen was not moved)
563 						// within the given time interval
564 						DPRINTF_INFO((ID "device_read(%p) name = \"%s%d\" -> "
565 							"B_TIMED_OUT\n", cookie, kBasePublishPath,
566 							device->number));
567 						*count = 8;
568 						read_header(device, buffer);
569 						ret = B_OK;
570 					} else {
571 						// any other error trying to acquire the semaphore
572 						*count = 0;
573 					}
574 				} else {
575 					if (device->status == 0/*B_USBD_SUCCESS*/) {
576 						DPRINTF_INFO((ID "interrupt transfer - success\n"));
577 						// copy the data from the buffer
578 						dataLength = min_c(device->length, *count - 8);
579 						*count = dataLength + 8;
580 						read_header(device, buffer);
581 						memcpy(buffer + 8, device->data, dataLength);
582 					} else {
583 						// an error happened during the interrupt transfer
584 						*count = 0;
585 						dprintf(ID "interrupt transfer - "
586 							"failure: %" B_PRIu32 "\n", device->status);
587 						ret = B_ERROR;
588 					}
589 				}
590 			} else {
591 				*count = 0;
592 				dprintf(ID "device_read(%p) name = \"%s%d\" -> error queuing "
593 					"interrupt: %" B_PRId32 "\n", cookie, kBasePublishPath,
594 					device->number, ret);
595 			}
596 		} else if (*count == 8) {
597 			read_header(device, buffer);
598 			ret = B_OK;
599 		} else {
600 			dprintf(ID "device_read(%p) name = \"%s%d\" -> buffer size must be "
601 				"at least 8 bytes!\n", cookie, kBasePublishPath,
602 				device->number);
603 			*count = 0;
604 			ret = B_BAD_VALUE;
605 		}
606 	}
607 
608 	return ret;
609 }
610 
611 // device_write
612 static status_t
613 device_write(void *cookie, off_t pos, const void *buf, size_t *count)
614 {
615 	return B_ERROR;
616 }
617 
618 // device_control
619 static status_t
620 device_control (void *cookie, uint32 msg, void *arg1, size_t len)
621 {
622 	return B_ERROR;
623 }
624 
625 // #pragma mark - Driver Hooks
626 //
627 // These functions provide the glue used by DevFS to load/unload
628 // the driver and also handle registering with the USB bus manager
629 // to receive device added and removed events
630 
631 static usb_notify_hooks notify_hooks =
632 {
633 	&device_added,
634 	&device_removed
635 };
636 
637 static const usb_support_descriptor kSupportedDevices[] =
638 {
639 	{ 3, 1, 2, 0, 0 }
640 };
641 
642 // init_hardware
643 status_t
644 init_hardware(void)
645 {
646 	return B_OK;
647 }
648 
649 // init_driver
650 status_t
651 init_driver(void)
652 {
653 	DPRINTF_INFO((ID "init_driver(), built %s %s\n", __DATE__, __TIME__));
654 
655 #if DEBUG_DRIVER && !defined(__HAIKU__)
656 	if (load_driver_symbols(kDriverName) == B_OK) {
657 		DPRINTF_INFO((ID "loaded symbols\n"));
658 	} else {
659 		DPRINTF_ERR((ID "no driver symbols loaded!\n"));
660 	}
661 #endif
662 
663 	if (get_module(B_USB_MODULE_NAME, (module_info**) &usb) != B_OK) {
664 		DPRINTF_ERR((ID "cannot get module \"%s\"\n", B_USB_MODULE_NAME));
665 		return B_ERROR;
666 	}
667 
668 	if ((sDeviceListLock = create_sem(1,"sDeviceListLock")) < 0) {
669 		put_module(B_USB_MODULE_NAME);
670 		return sDeviceListLock;
671 	}
672 
673 	usb->register_driver(kDriverName, kSupportedDevices, 1, NULL);
674 	usb->install_notify(kDriverName, &notify_hooks);
675 
676 	return B_OK;
677 }
678 
679 // uninit_driver
680 void
681 uninit_driver(void)
682 {
683 	int i;
684 
685 	DPRINTF_INFO((ID "uninit_driver()\n"));
686 
687 	usb->uninstall_notify(kDriverName);
688 
689 	delete_sem(sDeviceListLock);
690 
691 	put_module(B_USB_MODULE_NAME);
692 
693 	if (sDeviceNames) {
694 		for (i = 0; sDeviceNames[i]; i++)
695 			free(sDeviceNames[i]);
696 		free(sDeviceNames);
697 	}
698 
699 	DPRINTF_INFO((ID "uninit_driver() done\n"));
700 }
701 
702 // publish_devices
703 const char**
704 publish_devices()
705 {
706 	wacom_device *device;
707 	int i;
708 
709 	DPRINTF_INFO((ID "publish_devices()\n"));
710 
711 	if (sDeviceNames) {
712 		for (i = 0; sDeviceNames[i]; i++)
713 			free((char *) sDeviceNames[i]);
714 		free(sDeviceNames);
715 	}
716 
717 	acquire_sem(sDeviceListLock);
718 	sDeviceNames = (char**)malloc(sizeof(char*) * (sDeviceCount + 2));
719 	if (sDeviceNames) {
720 		for (i = 0, device = sDeviceList; device; device = device->next) {
721 			sDeviceNames[i] = (char*)malloc(strlen(kBasePublishPath) + 4);
722 			if (sDeviceNames[i]) {
723 				sprintf(sDeviceNames[i],"%s%d",kBasePublishPath,device->number);
724 				DPRINTF_INFO((ID "publishing: \"/dev/%s\"\n",sDeviceNames[i]));
725 				i++;
726 			}
727 		}
728 		// publish the currently fake control device
729 		sDeviceNames[i] = (char*)malloc(strlen(kBasePublishPath) + 8);
730 		if (sDeviceNames[i])
731 			sprintf(sDeviceNames[i], "%s%s", kBasePublishPath, "control");
732 
733 		sDeviceNames[i + 1] = NULL;
734 	}
735 
736 	release_sem(sDeviceListLock);
737 
738 	return (const char**)sDeviceNames;
739 }
740 
741 static device_hooks sDeviceHooks = {
742 	device_open,
743 	device_close,
744 	device_free,
745 	device_control,
746 	device_read,
747 	device_write
748 };
749 
750 // find_device
751 device_hooks*
752 find_device(const char* name)
753 {
754 	return &sDeviceHooks;
755 }
756