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