xref: /haiku/src/add-ons/kernel/drivers/input/wacom/wacom.c (revision 4c8e85b316c35a9161f5a1c50ad70bc91c83a76f)
1 /*
2  * Copyright 2005-2020, 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 #include <kernel.h>
22 #include <wacom_driver.h>
23 
24 int32 api_version = B_CUR_DRIVER_API_VERSION;
25 
26 #define DEBUG_DRIVER 0
27 
28 #if DEBUG_DRIVER
29 #	define DPRINTF_INFO(x) dprintf x;
30 #	define DPRINTF_ERR(x) dprintf x;
31 #else
32 #	define DPRINTF_INFO(x)
33 #	define DPRINTF_ERR(x) dprintf x;
34 #endif
35 
36 typedef struct wacom_device wacom_device;
37 
38 struct wacom_device {
39 	wacom_device*		next;
40 
41 	int					open;
42 	int					number;
43 
44 	usb_device			dev;
45 
46 	usb_pipe			pipe;
47 	char*				data;
48 	size_t				max_packet_size;
49 	size_t				length;
50 
51 	uint16				vendor;
52 	uint16				product;
53 
54 	sem_id				notify_lock;
55 	uint32				status;
56 };
57 
58 // handy strings for referring to ourself
59 #define ID "wacom: "
60 static const char* kDriverName = "wacom";
61 static const char* kBasePublishPath = "input/wacom/usb/";
62 
63 // list of device instances and names for publishing
64 static wacom_device* sDeviceList = NULL;
65 static sem_id sDeviceListLock = -1;
66 static int sDeviceCount = 0;
67 
68 static char** sDeviceNames = NULL;
69 
70 // handle for the USB bus manager
71 static usb_module_info* usb;
72 
73 // These rather inelegant routines are used to assign numbers to
74 // device instances so that they have unique names in devfs.
75 
76 static uint32 sDeviceNumbers = 0;
77 
78 // get_number
79 static int
80 get_number()
81 {
82 	int num;
83 
84 	for (num = 0; num < 32; num++) {
85 		if (!(sDeviceNumbers & (1 << num))) {
86 			sDeviceNumbers |= (1 << num);
87 			return num;
88 		}
89 	}
90 
91 	return -1;
92 }
93 
94 // put_number
95 static void
96 put_number(int num)
97 {
98 	sDeviceNumbers &= ~(1 << num);
99 }
100 
101 // #pragma mark - Device addition and removal
102 //
103 // add_device() and remove_device() are used to create and tear down
104 // device instances.  They are used from the callbacks device_added()
105 // and device_removed() which are invoked by the USB bus manager.
106 
107 // add_device
108 static wacom_device*
109 add_device(usb_device dev)
110 {
111 	wacom_device *device = NULL;
112 	int num;
113 	size_t ifc, alt;
114 	const usb_interface_info *ii;
115 	status_t st;
116 	const usb_device_descriptor* udd;
117 	const usb_configuration_info *conf;
118 
119 	// we need these four for a Wacom tablet
120 	size_t controlTransferLength;
121 	int tryCount;
122 	char repData[2] = { 0x02, 0x02 };
123 	char retData[2] = { 0x00, 0x00 };
124 
125 	conf = usb->get_configuration(dev);
126 	DPRINTF_INFO((ID "add_device(%ld, %p)\n", dev, conf));
127 
128 	if ((num = get_number()) < 0)
129 		return NULL;
130 
131 	udd = usb->get_device_descriptor(dev);
132 	// only pick up wacom tablets
133 	if (udd && udd->vendor_id == 0x056a) {
134 
135 		DPRINTF_ERR((ID "add_device() - wacom detected\n"));
136 
137 		// see if the device has been configured already
138 		if (!conf) {
139 			conf = usb->get_nth_configuration(dev, 0);
140 		}
141 
142 		if (!conf)
143 			goto fail;
144 
145 		for (ifc = 0; ifc < conf->interface_count; ifc++) {
146 			DPRINTF_INFO((ID "add_device() - examining interface: %d\n", ifc));
147 			for (alt = 0; alt < conf->interface[ifc].alt_count; alt++) {
148 				ii = &conf->interface[ifc].alt[alt];
149 				DPRINTF_INFO((ID "add_device() - examining alt interface: "
150 					"%d\n", alt));
151 
152 
153 				// does it have the correct type of interface?
154 				if (ii->descr->interface_class != 3) continue;
155 				if (ii->descr->interface_subclass != 1) continue;
156 				if (ii->endpoint_count != 1) continue;
157 
158 				// only accept input endpoints
159 				if (ii->endpoint[0].descr->endpoint_address & 0x80) {
160 					DPRINTF_INFO((ID "add_device() - found input endpoint\n"));
161 					goto got_one;
162 				}
163 			}
164 		}
165 	} else
166 		goto fail;
167 
168 fail:
169 	put_number(num);
170 	if (device) {
171 		free(device->data);
172 		free(device);
173 	}
174 	return NULL;
175 
176 got_one:
177 	if ((device = (wacom_device *) malloc(sizeof(wacom_device))) == NULL)
178 		goto fail;
179 
180 	device->dev = dev;
181 	device->number = num;
182 	device->open = 1;
183 	device->notify_lock = -1;
184 	device->data = NULL;
185 
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 	// configure the rest of the wacom_device
275 	device->pipe = ii->endpoint[0].handle;
276 //DPRINTF_INFO((ID "add_device() - pipe id = %ld\n", device->pipe));
277 	device->length = 0;
278 	device->max_packet_size = ii->endpoint[0].descr->max_packet_size;
279 	device->data = (char*)malloc(device->max_packet_size);
280 	if (device->data == NULL)
281 		goto fail;
282 //DPRINTF_INFO((ID "add_device() - max packet length = %ld\n",
283 //	device->max_packet_size));
284 	device->status = 0;//B_USB_STATUS_SUCCESS;
285 	device->vendor = udd->vendor_id;
286 	device->product = udd->product_id;
287 
288 	DPRINTF_INFO((ID "add_device() - added %p (/dev/%s%d)\n", device,
289 		kBasePublishPath, num));
290 
291 	// add it to the list of devices so it will be published, etc
292 	acquire_sem(sDeviceListLock);
293 	device->next = sDeviceList;
294 	sDeviceList = device;
295 	sDeviceCount++;
296 	release_sem(sDeviceListLock);
297 
298 	return device;
299 }
300 
301 // remove_device
302 static void
303 remove_device(wacom_device *device)
304 {
305 	put_number(device->number);
306 
307 	usb->cancel_queued_transfers(device->pipe);
308 
309 	delete_sem(device->notify_lock);
310 	if (device->data)
311 		free(device->data);
312 	free(device);
313 }
314 
315 // device_added
316 static status_t
317 device_added(usb_device dev, void** cookie)
318 {
319 	wacom_device* device;
320 
321 	DPRINTF_INFO((ID "device_added(%ld,...)\n", dev));
322 
323 	// first see, if this device is already added
324 	acquire_sem(sDeviceListLock);
325 	for (device = sDeviceList; device; device = device->next) {
326 		DPRINTF_ERR((ID "device_added() - old device: %" B_PRIu32 "\n",
327 			device->dev));
328 		if (device->dev == dev) {
329 			DPRINTF_ERR((ID "device_added() - already added - done!\n"));
330 			*cookie = (void*)device;
331 			release_sem(sDeviceListLock);
332 			return B_OK;
333 		}
334 	}
335 	release_sem(sDeviceListLock);
336 
337 	if ((device = add_device(dev)) != NULL) {
338 		*cookie = (void*)device;
339 		DPRINTF_INFO((ID "device_added() - done!\n"));
340 		return B_OK;
341 	} else
342 		DPRINTF_INFO((ID "device_added() - failed to add device!\n"));
343 
344 	return B_ERROR;
345 }
346 
347 // device_removed
348 static status_t
349 device_removed(void *cookie)
350 {
351 	wacom_device *device = (wacom_device *) cookie;
352 
353 	DPRINTF_INFO((ID "device_removed(%p)\n", device));
354 
355 	if (device) {
356 
357 		acquire_sem(sDeviceListLock);
358 
359 		// remove it from the list of devices
360 		if (device == sDeviceList) {
361 			sDeviceList = device->next;
362 		} else {
363 			wacom_device *n;
364 			for (n = sDeviceList; n; n = n->next) {
365 				if (n->next == device) {
366 					n->next = device->next;
367 					break;
368 				}
369 			}
370 		}
371 		sDeviceCount--;
372 
373 		// tear it down if it's not open --
374 		// otherwise the last device_free() will handle it
375 
376 		device->open--;
377 
378 		DPRINTF_ERR((ID "device_removed() open: %d\n", device->open));
379 
380 		if (device->open == 0) {
381 			remove_device(device);
382 		} else {
383 			dprintf(ID "device /dev/%s%d still open -- marked for removal\n",
384 				kBasePublishPath, device->number);
385 		}
386 
387 		release_sem(sDeviceListLock);
388 	}
389 
390 	return B_OK;
391 }
392 
393 // #pragma mark - Device Hooks
394 //
395 // Here we implement the posixy driver hooks (open/close/read/write/ioctl)
396 
397 // device_open
398 static status_t
399 device_open(const char *dname, uint32 flags, void** cookie)
400 {
401 	wacom_device *device;
402 	int n;
403 	status_t ret = B_ERROR;
404 
405 	n = atoi(dname + strlen(kBasePublishPath));
406 
407 	DPRINTF_INFO((ID "device_open(\"%s\",%d,...)\n", dname, flags));
408 
409 	acquire_sem(sDeviceListLock);
410 	for (device = sDeviceList; device; device = device->next) {
411 		if (device->number == n) {
412 //			if (device->open <= 1) {
413 				device->open++;
414 				*cookie = device;
415 				DPRINTF_ERR((ID "device_open() open: %d\n", device->open));
416 
417 				if (device->notify_lock < 0) {
418 					device->notify_lock = create_sem(0, "notify_lock");
419 					if (device->notify_lock < 0) {
420 						ret = device->notify_lock;
421 						device->open--;
422 						*cookie = NULL;
423 						dprintf(ID "device_open() -> "
424 							"create_sem() returns %" B_PRId32 "\n", ret);
425 					} else {
426 						ret = B_OK;
427 					}
428 				}
429 				release_sem(sDeviceListLock);
430 				return ret;
431 //			} else {
432 //				dprintf(ID "device_open() -> device is already open %ld\n",
433 //					ret);
434 //				release_sem(sDeviceListLock);
435 //				return B_ERROR;
436 //			}
437 		}
438 	}
439 	release_sem(sDeviceListLock);
440 	return ret;
441 }
442 
443 // device_close
444 static status_t
445 device_close (void *cookie)
446 {
447 #if DEBUG_DRIVER
448 	wacom_device *device = (wacom_device*) cookie;
449 	DPRINTF_ERR((ID "device_close() name = \"%s%d\"\n", kBasePublishPath,
450 		device->number));
451 #endif
452 	return B_OK;
453 }
454 
455 // device_free
456 static status_t
457 device_free(void *cookie)
458 {
459 	wacom_device *device = (wacom_device *)cookie;
460 
461 	DPRINTF_INFO((ID "device_free() name = \"%s%d\"\n", kBasePublishPath,
462 		device->number));
463 
464 	acquire_sem(sDeviceListLock);
465 
466 	device->open--;
467 
468 	DPRINTF_INFO((ID "device_free() open: %ld\n", device->open));
469 
470 	if (device->open == 0) {
471 		remove_device(device);
472 	}
473 	release_sem(sDeviceListLock);
474 
475 	return B_OK;
476 }
477 
478 // device_interrupt_callback
479 static void
480 device_interrupt_callback(void* cookie, status_t status, void* data,
481 	size_t actualLength)
482 {
483 	wacom_device* device = (wacom_device*)cookie;
484 	size_t length = min_c(actualLength, device->max_packet_size);
485 
486 	DPRINTF_INFO((ID "device_interrupt_callback(%p) name = \"%s%d\" -> "
487 		"status: %ld, length: %zu\n", cookie, kBasePublishPath, device->number,
488 		status, actualLength));
489 
490 	device->status = status;
491 	if (device->notify_lock >= 0) {
492 		if (status == B_OK) {
493 			memcpy(device->data, data, length);
494 			device->length = length;
495 		} else {
496 			device->length = 0;
497 		}
498 		release_sem(device->notify_lock);
499 	}
500 
501 	DPRINTF_INFO((ID "device_interrupt_callback() - done\n"));
502 }
503 
504 // read_header
505 static status_t
506 read_header(const wacom_device* device, void* buffer)
507 {
508 	wacom_device_header device_header;
509 	device_header.vendor_id = device->vendor;
510 	device_header.product_id = device->product;
511 	device_header.max_packet_size = device->max_packet_size;
512 
513 	if (!IS_USER_ADDRESS(buffer)) {
514 		memcpy(buffer, &device_header, sizeof(wacom_device_header));
515 		return B_OK;
516 	}
517 
518 	return user_memcpy(buffer, &device_header, sizeof(wacom_device_header));
519 }
520 
521 // device_read
522 static status_t
523 device_read(void* cookie, off_t pos, void* buf, size_t* count)
524 {
525 	wacom_device* device = (wacom_device*) cookie;
526 	status_t ret = B_BAD_VALUE;
527 	uint8* buffer = (uint8*)buf;
528 	uint32 dataLength;
529 
530 	if (!device)
531 		return ret;
532 
533 	ret = device->notify_lock;
534 
535 	DPRINTF_INFO((ID "device_read(%p,%Ld,0x%x,%d) name = \"%s%d\"\n",
536 			 cookie, pos, buf, *count, kBasePublishPath, device->number));
537 
538 	if (ret >= B_OK) {
539 		// what the client "reads" is decided depending on how much bytes are
540 		// provided. "sizeof(wacom_device_header)" bytes are needed to "read"
541 		// vendor id, product id and max packet size in case the client wants to
542 		// read more than "sizeof(wacom_device_header)" bytes, a usb interupt
543 		// transfer is scheduled, and an error report is returned as appropriate
544 		if (*count > sizeof(wacom_device_header)) {
545 			// queue the interrupt transfer
546 			ret = usb->queue_interrupt(device->pipe, device->data,
547 				device->max_packet_size, device_interrupt_callback, device);
548 			if (ret >= B_OK) {
549 				// we will block here until the interrupt transfer has been done
550 				ret = acquire_sem_etc(device->notify_lock, 1,
551 					B_RELATIVE_TIMEOUT, 500 * 1000);
552 				// handle time out
553 				if (ret < B_OK) {
554 					usb->cancel_queued_transfers(device->pipe);
555 					acquire_sem(device->notify_lock);
556 						// collect the sem released by the cancel
557 
558 					if (ret == B_TIMED_OUT) {
559 						// a time_out is ok, since it only means that the device
560 						// had nothing to report (ie mouse/pen was not moved)
561 						// within the given time interval
562 						DPRINTF_INFO((ID "device_read(%p) name = \"%s%d\" -> "
563 							"B_TIMED_OUT\n", cookie, kBasePublishPath,
564 							device->number));
565 						*count = sizeof(wacom_device_header);
566 						ret = read_header(device, buffer);
567 					} else {
568 						// any other error trying to acquire the semaphore
569 						*count = 0;
570 					}
571 				} else {
572 					if (device->status == 0/*B_USBD_SUCCESS*/) {
573 						DPRINTF_INFO((ID "interrupt transfer - success\n"));
574 						// copy the data from the buffer
575 						dataLength = min_c(device->length,
576 							*count - sizeof(wacom_device_header));
577 						*count = dataLength + sizeof(wacom_device_header);
578 						ret = read_header(device, buffer);
579 						if (ret == B_OK) {
580 							if (IS_USER_ADDRESS(buffer))
581 								ret = user_memcpy(
582 									buffer + sizeof(wacom_device_header),
583 									device->data, dataLength);
584 							else
585 								memcpy(buffer + sizeof(wacom_device_header),
586 									device->data, dataLength);
587 						}
588 					} else {
589 						// an error happened during the interrupt transfer
590 						*count = 0;
591 						dprintf(ID "interrupt transfer - "
592 							"failure: %" B_PRIu32 "\n", device->status);
593 						ret = B_ERROR;
594 					}
595 				}
596 			} else {
597 				*count = 0;
598 				dprintf(ID "device_read(%p) name = \"%s%d\" -> error queuing "
599 					"interrupt: %" B_PRId32 "\n", cookie, kBasePublishPath,
600 					device->number, ret);
601 			}
602 		} else if (*count == sizeof(wacom_device_header)) {
603 			ret = read_header(device, buffer);
604 		} else {
605 			dprintf(ID "device_read(%p) name = \"%s%d\" -> buffer size must be "
606 				"at least the size of the wacom_device_header struct!\n",
607 				cookie, kBasePublishPath, device->number);
608 			*count = 0;
609 			ret = B_BAD_VALUE;
610 		}
611 	}
612 
613 	return ret;
614 }
615 
616 // device_write
617 static status_t
618 device_write(void *cookie, off_t pos, const void *buf, size_t *count)
619 {
620 	return B_ERROR;
621 }
622 
623 // device_control
624 static status_t
625 device_control (void *cookie, uint32 msg, void *arg1, size_t len)
626 {
627 	return B_ERROR;
628 }
629 
630 // #pragma mark - Driver Hooks
631 //
632 // These functions provide the glue used by DevFS to load/unload
633 // the driver and also handle registering with the USB bus manager
634 // to receive device added and removed events
635 
636 static usb_notify_hooks notify_hooks =
637 {
638 	&device_added,
639 	&device_removed
640 };
641 
642 static const usb_support_descriptor kSupportedDevices[] =
643 {
644 	{ 3, 1, 2, 0, 0 }
645 };
646 
647 // init_hardware
648 status_t
649 init_hardware(void)
650 {
651 	return B_OK;
652 }
653 
654 // init_driver
655 status_t
656 init_driver(void)
657 {
658 	DPRINTF_INFO((ID "init_driver(), built %s %s\n", __DATE__, __TIME__));
659 
660 #if DEBUG_DRIVER && !defined(__HAIKU__)
661 	if (load_driver_symbols(kDriverName) == B_OK) {
662 		DPRINTF_INFO((ID "loaded symbols\n"));
663 	} else {
664 		DPRINTF_ERR((ID "no driver symbols loaded!\n"));
665 	}
666 #endif
667 
668 	if (get_module(B_USB_MODULE_NAME, (module_info**) &usb) != B_OK) {
669 		DPRINTF_ERR((ID "cannot get module \"%s\"\n", B_USB_MODULE_NAME));
670 		return B_ERROR;
671 	}
672 
673 	if ((sDeviceListLock = create_sem(1,"sDeviceListLock")) < 0) {
674 		put_module(B_USB_MODULE_NAME);
675 		return sDeviceListLock;
676 	}
677 
678 	usb->register_driver(kDriverName, kSupportedDevices, 1, NULL);
679 	usb->install_notify(kDriverName, &notify_hooks);
680 
681 	return B_OK;
682 }
683 
684 // uninit_driver
685 void
686 uninit_driver(void)
687 {
688 	int i;
689 
690 	DPRINTF_INFO((ID "uninit_driver()\n"));
691 
692 	usb->uninstall_notify(kDriverName);
693 
694 	delete_sem(sDeviceListLock);
695 
696 	put_module(B_USB_MODULE_NAME);
697 
698 	if (sDeviceNames) {
699 		for (i = 0; sDeviceNames[i]; i++)
700 			free(sDeviceNames[i]);
701 		free(sDeviceNames);
702 	}
703 
704 	DPRINTF_INFO((ID "uninit_driver() done\n"));
705 }
706 
707 // publish_devices
708 const char**
709 publish_devices()
710 {
711 	wacom_device *device;
712 	int i;
713 
714 	DPRINTF_INFO((ID "publish_devices()\n"));
715 
716 	if (sDeviceNames) {
717 		for (i = 0; sDeviceNames[i]; i++)
718 			free((char *) sDeviceNames[i]);
719 		free(sDeviceNames);
720 	}
721 
722 	acquire_sem(sDeviceListLock);
723 	sDeviceNames = (char**)malloc(sizeof(char*) * (sDeviceCount + 2));
724 	if (sDeviceNames) {
725 		for (i = 0, device = sDeviceList; device; device = device->next) {
726 			sDeviceNames[i] = (char*)malloc(strlen(kBasePublishPath) + 4);
727 			if (sDeviceNames[i]) {
728 				sprintf(sDeviceNames[i],"%s%d",kBasePublishPath,device->number);
729 				DPRINTF_INFO((ID "publishing: \"/dev/%s\"\n",sDeviceNames[i]));
730 				i++;
731 			}
732 		}
733 		sDeviceNames[i] = 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