xref: /haiku/src/system/kernel/device_manager/legacy_drivers.cpp (revision db6fcb750a1afb5fdc752322972adf6044d3b4c4)
1 /*
2  * Copyright 2002-2011, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "legacy_drivers.h"
8 
9 #include <dirent.h>
10 #include <errno.h>
11 #include <new>
12 #include <stdio.h>
13 
14 #include <FindDirectory.h>
15 #include <image.h>
16 #include <NodeMonitor.h>
17 
18 #include <boot_device.h>
19 #include <boot/kernel_args.h>
20 #include <elf.h>
21 #include <find_directory_private.h>
22 #include <fs/devfs.h>
23 #include <fs/KPath.h>
24 #include <fs/node_monitor.h>
25 #include <Notifications.h>
26 #include <safemode.h>
27 #include <util/DoublyLinkedList.h>
28 #include <util/OpenHashTable.h>
29 #include <util/Stack.h>
30 #include <vfs.h>
31 
32 #include "AbstractModuleDevice.h"
33 #include "devfs_private.h"
34 
35 
36 //#define TRACE_LEGACY_DRIVERS
37 #ifdef TRACE_LEGACY_DRIVERS
38 #	define TRACE(x) dprintf x
39 #else
40 #	define TRACE(x)
41 #endif
42 
43 #define DRIVER_HASH_SIZE 16
44 
45 
46 namespace {
47 
48 struct legacy_driver;
49 
50 class LegacyDevice : public AbstractModuleDevice,
51 	public DoublyLinkedListLinkImpl<LegacyDevice> {
52 public:
53 							LegacyDevice(legacy_driver* driver,
54 								const char* path, device_hooks* hooks);
55 	virtual					~LegacyDevice();
56 
57 			status_t		InitCheck() const;
58 
59 	virtual	status_t		InitDevice();
60 	virtual	void			UninitDevice();
61 
62 	virtual	void			Removed();
63 
64 			void			SetHooks(device_hooks* hooks);
65 
66 			legacy_driver*	Driver() const { return fDriver; }
67 			const char*		Path() const { return fPath; }
68 			device_hooks*	Hooks() const { return fHooks; }
69 
70 	virtual	status_t		Open(const char* path, int openMode,
71 								void** _cookie);
72 	virtual	status_t		Select(void* cookie, uint8 event, selectsync* sync);
73 
74 			bool			Republished() const { return fRepublished; }
75 			void			SetRepublished(bool republished)
76 								{ fRepublished = republished; }
77 
78 			void			SetRemovedFromParent(bool removed)
79 								{ fRemovedFromParent = removed; }
80 
81 private:
82 	legacy_driver*			fDriver;
83 	const char*				fPath;
84 	device_hooks*			fHooks;
85 	bool					fRepublished;
86 	bool					fRemovedFromParent;
87 };
88 
89 typedef DoublyLinkedList<LegacyDevice> DeviceList;
90 
91 struct legacy_driver {
92 	legacy_driver*	next;
93 	const char*		path;
94 	const char*		name;
95 	dev_t			device;
96 	ino_t			node;
97 	timespec		last_modified;
98 	image_id		image;
99 	uint32			devices_used;
100 	bool			binary_updated;
101 	int32			priority;
102 	DeviceList		devices;
103 
104 	// driver image information
105 	int32			api_version;
106 	device_hooks*	(*find_device)(const char *);
107 	const char**	(*publish_devices)(void);
108 	status_t		(*uninit_driver)(void);
109 	status_t		(*uninit_hardware)(void);
110 };
111 
112 
113 enum driver_event_type {
114 	kAddDriver,
115 	kRemoveDriver,
116 	kAddWatcher,
117 	kRemoveWatcher
118 };
119 
120 struct driver_event : DoublyLinkedListLinkImpl<driver_event> {
121 	driver_event(driver_event_type _type) : type(_type) {}
122 
123 	struct ref {
124 		dev_t		device;
125 		ino_t		node;
126 	};
127 
128 	driver_event_type	type;
129 	union {
130 		char			path[B_PATH_NAME_LENGTH];
131 		ref				node;
132 	};
133 };
134 
135 typedef DoublyLinkedList<driver_event> DriverEventList;
136 
137 
138 struct driver_entry : DoublyLinkedListLinkImpl<driver_entry> {
139 	char*			path;
140 	dev_t			device;
141 	ino_t			node;
142 	int32			busses;
143 };
144 
145 typedef DoublyLinkedList<driver_entry> DriverEntryList;
146 
147 
148 struct node_entry : DoublyLinkedListLinkImpl<node_entry> {
149 };
150 
151 typedef DoublyLinkedList<node_entry> NodeList;
152 
153 
154 struct directory_node_entry {
155 	directory_node_entry*	hash_link;
156 	ino_t					node;
157 };
158 
159 struct DirectoryNodeHashDefinition {
160 	typedef ino_t* KeyType;
161 	typedef directory_node_entry ValueType;
162 
163 	size_t HashKey(ino_t* key) const
164 		{ return _Hash(*key); }
165 	size_t Hash(directory_node_entry* entry) const
166 		{ return _Hash(entry->node); }
167 	bool Compare(ino_t* key, directory_node_entry* entry) const
168 		{ return *key == entry->node; }
169 	directory_node_entry*&
170 		GetLink(directory_node_entry* entry) const
171 		{ return entry->hash_link; }
172 
173 	uint32 _Hash(ino_t node) const
174 		{ return (uint32)(node >> 32) + (uint32)node; }
175 };
176 
177 typedef BOpenHashTable<DirectoryNodeHashDefinition> DirectoryNodeHash;
178 
179 class DirectoryIterator {
180 public:
181 						DirectoryIterator(const char *path,
182 							const char *subPath = NULL, bool recursive = false);
183 						~DirectoryIterator();
184 
185 			void		SetTo(const char *path, const char *subPath = NULL,
186 							bool recursive = false);
187 
188 			status_t	GetNext(KPath &path, struct stat &stat);
189 			const char*	CurrentName() const { return fCurrentName; }
190 
191 			void		Unset();
192 			void		AddPath(const char *path, const char *subPath = NULL);
193 
194 private:
195 	Stack<KPath*>		fPaths;
196 	bool				fRecursive;
197 	DIR*				fDirectory;
198 	KPath*				fBasePath;
199 	const char*			fCurrentName;
200 };
201 
202 
203 class DirectoryWatcher : public NotificationListener {
204 public:
205 						DirectoryWatcher();
206 	virtual				~DirectoryWatcher();
207 
208 	virtual void		EventOccurred(NotificationService& service,
209 							const KMessage* event);
210 };
211 
212 class DriverWatcher : public NotificationListener {
213 public:
214 						DriverWatcher();
215 	virtual				~DriverWatcher();
216 
217 	virtual void		EventOccurred(NotificationService& service,
218 							const KMessage* event);
219 };
220 
221 
222 struct DriverHash {
223 	typedef const char*			KeyType;
224 	typedef legacy_driver		ValueType;
225 
226 	size_t HashKey(KeyType key) const
227 	{
228 		return hash_hash_string(key);
229 	}
230 
231 	size_t Hash(ValueType* driver) const
232 	{
233 		return HashKey(driver->name);
234 	}
235 
236 	bool Compare(KeyType key, ValueType* driver) const
237 	{
238 		return strcmp(driver->name, key) == 0;
239 	}
240 
241 	ValueType*& GetLink(ValueType* value) const
242 	{
243 		return value->next;
244 	}
245 };
246 
247 typedef BOpenHashTable<DriverHash> DriverTable;
248 
249 
250 }	// unnamed namespace
251 
252 
253 static status_t unload_driver(legacy_driver *driver);
254 static status_t load_driver(legacy_driver *driver);
255 
256 
257 static const directory_which kDriverPaths[] = {
258 	B_USER_NONPACKAGED_ADDONS_DIRECTORY,
259 	B_USER_ADDONS_DIRECTORY,
260 	B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY,
261 	B_SYSTEM_ADDONS_DIRECTORY
262 };
263 
264 static DriverWatcher sDriverWatcher;
265 static int32 sDriverEventsPending;
266 static DriverEventList sDriverEvents;
267 static mutex sDriverEventsLock = MUTEX_INITIALIZER("driver events");
268 	// inner lock, protects the sDriverEvents list only
269 static DirectoryWatcher sDirectoryWatcher;
270 static DirectoryNodeHash sDirectoryNodeHash;
271 static recursive_lock sLock;
272 static bool sWatching;
273 
274 static DriverTable* sDriverHash;
275 
276 
277 //	#pragma mark - driver private
278 
279 
280 /*!	Collects all published devices of a driver, compares them to what the
281 	driver would publish now, and then publishes/unpublishes the devices
282 	as needed.
283 	If the driver does not publish any devices anymore, it is unloaded.
284 */
285 static status_t
286 republish_driver(legacy_driver* driver)
287 {
288 	if (driver->image < 0) {
289 		// The driver is not yet loaded - go through the normal load procedure
290 		return load_driver(driver);
291 	}
292 
293 	// mark all devices
294 	DeviceList::Iterator iterator = driver->devices.GetIterator();
295 	while (LegacyDevice* device = iterator.Next()) {
296 		device->SetRepublished(false);
297 	}
298 
299 	// now ask the driver for it's currently published devices
300 	const char** devicePaths = driver->publish_devices();
301 
302 	int32 exported = 0;
303 	for (; devicePaths != NULL && devicePaths[0]; devicePaths++) {
304 		LegacyDevice* device;
305 
306 		iterator = driver->devices.GetIterator();
307 		while ((device = iterator.Next()) != NULL) {
308 			if (!strncmp(device->Path(), devicePaths[0], B_PATH_NAME_LENGTH)) {
309 				// mark device as republished
310 				device->SetRepublished(true);
311 				exported++;
312 				break;
313 			}
314 		}
315 
316 		device_hooks* hooks = driver->find_device(devicePaths[0]);
317 		if (hooks == NULL)
318 			continue;
319 
320 		if (device != NULL) {
321 			// update hooks
322 			device->SetHooks(hooks);
323 			continue;
324 		}
325 
326 		// the device was not present before -> publish it now
327 		TRACE(("devfs: publishing new device \"%s\"\n", devicePaths[0]));
328 		device = new(std::nothrow) LegacyDevice(driver, devicePaths[0], hooks);
329 		if (device != NULL && device->InitCheck() == B_OK
330 			&& devfs_publish_device(devicePaths[0], device) == B_OK) {
331 			driver->devices.Add(device);
332 			exported++;
333 		} else
334 			delete device;
335 	}
336 
337 	// remove all devices that weren't republished
338 	iterator = driver->devices.GetIterator();
339 	while (LegacyDevice* device = iterator.Next()) {
340 		if (device->Republished())
341 			continue;
342 
343 		TRACE(("devfs: unpublishing no more present \"%s\"\n", device->Path()));
344 		iterator.Remove();
345 		device->SetRemovedFromParent(true);
346 
347 		devfs_unpublish_device(device, true);
348 	}
349 
350 	if (exported == 0 && driver->devices_used == 0) {
351 		TRACE(("devfs: driver \"%s\" does not publish any more nodes and is "
352 			"unloaded\n", driver->path));
353 		unload_driver(driver);
354 	}
355 
356 	return B_OK;
357 }
358 
359 
360 static status_t
361 load_driver(legacy_driver* driver)
362 {
363 	status_t (*init_hardware)(void);
364 	status_t (*init_driver)(void);
365 	status_t status;
366 
367 	driver->binary_updated = false;
368 
369 	// load the module
370 	image_id image = driver->image;
371 	if (image < 0) {
372 		image = load_kernel_add_on(driver->path);
373 		if (image < 0)
374 			return image;
375 	}
376 
377 	// For a valid device driver the following exports are required
378 
379 	int32* apiVersion;
380 	if (get_image_symbol(image, "api_version", B_SYMBOL_TYPE_DATA,
381 			(void**)&apiVersion) == B_OK) {
382 #if B_CUR_DRIVER_API_VERSION != 2
383 		// just in case someone decides to bump up the api version
384 #error Add checks here for new vs old api version!
385 #endif
386 		if (*apiVersion > B_CUR_DRIVER_API_VERSION) {
387 			dprintf("devfs: \"%s\" api_version %" B_PRId32 " not handled\n",
388 				driver->name, *apiVersion);
389 			status = B_BAD_VALUE;
390 			goto error1;
391 		}
392 		if (*apiVersion < 1) {
393 			dprintf("devfs: \"%s\" api_version invalid\n", driver->name);
394 			status = B_BAD_VALUE;
395 			goto error1;
396 		}
397 
398 		driver->api_version = *apiVersion;
399 	} else
400 		dprintf("devfs: \"%s\" api_version missing\n", driver->name);
401 
402 	if (get_image_symbol(image, "publish_devices", B_SYMBOL_TYPE_TEXT,
403 				(void**)&driver->publish_devices) != B_OK
404 		|| get_image_symbol(image, "find_device", B_SYMBOL_TYPE_TEXT,
405 				(void**)&driver->find_device) != B_OK) {
406 		dprintf("devfs: \"%s\" mandatory driver symbol(s) missing!\n",
407 			driver->name);
408 		status = B_BAD_VALUE;
409 		goto error1;
410 	}
411 
412 	// Init the driver
413 
414 	if (get_image_symbol(image, "init_hardware", B_SYMBOL_TYPE_TEXT,
415 			(void**)&init_hardware) == B_OK
416 		&& (status = init_hardware()) != B_OK) {
417 		TRACE(("%s: init_hardware() failed: %s\n", driver->name,
418 			strerror(status)));
419 		status = ENXIO;
420 		goto error1;
421 	}
422 
423 	if (get_image_symbol(image, "init_driver", B_SYMBOL_TYPE_TEXT,
424 			(void**)&init_driver) == B_OK
425 		&& (status = init_driver()) != B_OK) {
426 		TRACE(("%s: init_driver() failed: %s\n", driver->name,
427 			strerror(status)));
428 		status = ENXIO;
429 		goto error2;
430 	}
431 
432 	// resolve and cache those for the driver unload code
433 	if (get_image_symbol(image, "uninit_driver", B_SYMBOL_TYPE_TEXT,
434 		(void**)&driver->uninit_driver) != B_OK)
435 		driver->uninit_driver = NULL;
436 	if (get_image_symbol(image, "uninit_hardware", B_SYMBOL_TYPE_TEXT,
437 		(void**)&driver->uninit_hardware) != B_OK)
438 		driver->uninit_hardware = NULL;
439 
440 	// The driver has successfully been initialized, now we can
441 	// finally publish its device entries
442 
443 	driver->image = image;
444 	return republish_driver(driver);
445 
446 error2:
447 	if (driver->uninit_hardware)
448 		driver->uninit_hardware();
449 
450 error1:
451 	if (driver->image < 0) {
452 		unload_kernel_add_on(image);
453 		driver->image = status;
454 	}
455 
456 	return status;
457 }
458 
459 
460 static status_t
461 unload_driver(legacy_driver* driver)
462 {
463 	if (driver->image < 0) {
464 		// driver is not currently loaded
465 		return B_NO_INIT;
466 	}
467 
468 	if (driver->uninit_driver)
469 		driver->uninit_driver();
470 
471 	if (driver->uninit_hardware)
472 		driver->uninit_hardware();
473 
474 	unload_kernel_add_on(driver->image);
475 	driver->image = -1;
476 	driver->binary_updated = false;
477 	driver->find_device = NULL;
478 	driver->publish_devices = NULL;
479 	driver->uninit_driver = NULL;
480 	driver->uninit_hardware = NULL;
481 
482 	return B_OK;
483 }
484 
485 
486 /*!	Unpublishes all devices belonging to the \a driver. */
487 static void
488 unpublish_driver(legacy_driver* driver)
489 {
490 	while (LegacyDevice* device = driver->devices.RemoveHead()) {
491 		device->SetRemovedFromParent(true);
492 		devfs_unpublish_device(device, true);
493 	}
494 }
495 
496 
497 static void
498 change_driver_watcher(dev_t device, ino_t node, bool add)
499 {
500 	if (device == -1)
501 		return;
502 
503 	driver_event* event = new (std::nothrow) driver_event(
504 		add ? kAddWatcher : kRemoveWatcher);
505 	if (event == NULL)
506 		return;
507 
508 	event->node.device = device;
509 	event->node.node = node;
510 
511 	MutexLocker _(sDriverEventsLock);
512 	sDriverEvents.Add(event);
513 
514 	atomic_add(&sDriverEventsPending, 1);
515 }
516 
517 
518 static int32
519 get_priority(const char* path)
520 {
521 	// TODO: would it be better to initialize a static structure here
522 	// using find_directory()?
523 	const directory_which whichPath[] = {
524 		B_SYSTEM_DIRECTORY,
525 		B_SYSTEM_NONPACKAGED_DIRECTORY,
526 		B_USER_DIRECTORY
527 	};
528 	KPath pathBuffer;
529 
530 	for (uint32 index = 0; index < sizeof(whichPath) / sizeof(whichPath[0]);
531 			index++) {
532 		if (__find_directory(whichPath[index], gBootDevice, false,
533 			pathBuffer.LockBuffer(), pathBuffer.BufferSize()) == B_OK) {
534 			pathBuffer.UnlockBuffer();
535 			if (!strncmp(pathBuffer.Path(), path, pathBuffer.BufferSize()))
536 				return index;
537 		} else
538 			pathBuffer.UnlockBuffer();
539 	}
540 
541 	return -1;
542 }
543 
544 
545 static const char*
546 get_leaf(const char* path)
547 {
548 	const char* name = strrchr(path, '/');
549 	if (name == NULL)
550 		return path;
551 
552 	return name + 1;
553 }
554 
555 
556 static legacy_driver*
557 find_driver(dev_t device, ino_t node)
558 {
559 	DriverTable::Iterator iterator(sDriverHash);
560 	while (iterator.HasNext()) {
561 		legacy_driver* driver = iterator.Next();
562 		if (driver->device == device && driver->node == node)
563 			return driver;
564 	}
565 
566 	return NULL;
567 }
568 
569 
570 static status_t
571 add_driver(const char* path, image_id image)
572 {
573 	// Check if we already know this driver
574 
575 	struct stat stat;
576 	if (image >= 0) {
577 		// The image ID should be a small number and hopefully the boot FS
578 		// doesn't use small negative values -- if it is inode based, we should
579 		// be relatively safe.
580 		stat.st_dev = -1;
581 		stat.st_ino = -1;
582 	} else {
583 		if (::stat(path, &stat) != 0)
584 			return errno;
585 	}
586 
587 	int32 priority = get_priority(path);
588 
589 	RecursiveLocker _(sLock);
590 
591 	legacy_driver* driver = sDriverHash->Lookup(get_leaf(path));
592 	if (driver != NULL) {
593 		// we know this driver
594 		if (strcmp(driver->path, path) != 0) {
595 			// TODO: do properly, but for now we just update the path if it
596 			// isn't the same anymore so rescanning of drivers will work in
597 			// case this driver was loaded so early that it has a boot module
598 			// path and not a proper driver path
599 			free((char*)driver->path);
600 			driver->path = strdup(path);
601 			driver->name = get_leaf(driver->path);
602 			driver->binary_updated = true;
603 		}
604 
605 		// TODO: check if this driver is a different one and has precendence
606 		// (ie. common supersedes system).
607 		//dprintf("new driver has priority %ld, old %ld\n", priority, driver->priority);
608 		if (priority >= driver->priority) {
609 			driver->binary_updated = true;
610 			return B_OK;
611 		}
612 
613 		// TODO: test for changes here and/or via node monitoring and reload
614 		//	the driver if necessary
615 		if (driver->image < B_OK)
616 			return driver->image;
617 
618 		return B_OK;
619 	}
620 
621 	// we don't know this driver, create a new entry for it
622 
623 	driver = (legacy_driver*)malloc(sizeof(legacy_driver));
624 	if (driver == NULL)
625 		return B_NO_MEMORY;
626 
627 	driver->path = strdup(path);
628 	if (driver->path == NULL) {
629 		free(driver);
630 		return B_NO_MEMORY;
631 	}
632 
633 	driver->name = get_leaf(driver->path);
634 	driver->device = stat.st_dev;
635 	driver->node = stat.st_ino;
636 	driver->image = image;
637 	driver->last_modified = stat.st_mtim;
638 	driver->devices_used = 0;
639 	driver->binary_updated = false;
640 	driver->priority = priority;
641 
642 	driver->api_version = 1;
643 	driver->find_device = NULL;
644 	driver->publish_devices = NULL;
645 	driver->uninit_driver = NULL;
646 	driver->uninit_hardware = NULL;
647 	new(&driver->devices) DeviceList;
648 
649 	sDriverHash->Insert(driver);
650 	if (stat.st_dev > 0)
651 		change_driver_watcher(stat.st_dev, stat.st_ino, true);
652 
653 	// Even if loading the driver fails - its entry will stay with us
654 	// so that we don't have to go through it again
655 	return load_driver(driver);
656 }
657 
658 
659 /*!	This is no longer part of the public kernel API, so we just export the
660 	symbol
661 */
662 extern "C" status_t load_driver_symbols(const char* driverName);
663 status_t
664 load_driver_symbols(const char* driverName)
665 {
666 	// This is done globally for the whole kernel via the settings file.
667 	// We don't have to do anything here.
668 
669 	return B_OK;
670 }
671 
672 
673 static status_t
674 reload_driver(legacy_driver* driver)
675 {
676 	dprintf("devfs: reload driver \"%s\" (%" B_PRIdDEV ", %" B_PRIdINO ")\n",
677 		driver->name, driver->device, driver->node);
678 
679 	unload_driver(driver);
680 
681 	struct stat stat;
682 	if (::stat(driver->path, &stat) == 0
683 		&& (stat.st_dev != driver->device || stat.st_ino != driver->node)) {
684 		// The driver file has been changed, so we need to update its listener
685 		change_driver_watcher(driver->device, driver->node, false);
686 
687 		driver->device = stat.st_dev;
688 		driver->node = stat.st_ino;
689 
690 		change_driver_watcher(driver->device, driver->node, true);
691 	}
692 
693 	status_t status = load_driver(driver);
694 	if (status != B_OK)
695 		unpublish_driver(driver);
696 
697 	return status;
698 }
699 
700 
701 static void
702 handle_driver_events(void* /*_fs*/, int /*iteration*/)
703 {
704 	if (atomic_and(&sDriverEventsPending, 0) == 0)
705 		return;
706 
707 	// something happened, let's see what it was
708 
709 	while (true) {
710 		MutexLocker eventLocker(sDriverEventsLock);
711 
712 		driver_event* event = sDriverEvents.RemoveHead();
713 		if (event == NULL)
714 			break;
715 
716 		eventLocker.Unlock();
717 		TRACE(("driver event %p, type %d\n", event, event->type));
718 
719 		switch (event->type) {
720 			case kAddDriver:
721 			{
722 				// Add new drivers
723 				RecursiveLocker locker(sLock);
724 				TRACE(("  add driver %p\n", event->path));
725 
726 				legacy_driver* driver = sDriverHash->Lookup(
727 					get_leaf(event->path));
728 				if (driver == NULL)
729 					legacy_driver_add(event->path);
730 				else if (get_priority(event->path) >= driver->priority)
731 					driver->binary_updated = true;
732 				break;
733 			}
734 
735 			case kRemoveDriver:
736 			{
737 				// Mark removed drivers as updated
738 				RecursiveLocker locker(sLock);
739 				TRACE(("  remove driver %p\n", event->path));
740 
741 				legacy_driver* driver = sDriverHash->Lookup(
742 					get_leaf(event->path));
743 				if (driver != NULL
744 					&& get_priority(event->path) >= driver->priority)
745 					driver->binary_updated = true;
746 				break;
747 			}
748 
749 			case kAddWatcher:
750 				TRACE(("  add watcher %ld:%lld\n", event->node.device,
751 					event->node.node));
752 				add_node_listener(event->node.device, event->node.node,
753 					B_WATCH_STAT | B_WATCH_NAME, sDriverWatcher);
754 				break;
755 
756 			case kRemoveWatcher:
757 				TRACE(("  remove watcher %ld:%lld\n", event->node.device,
758 					event->node.node));
759 				remove_node_listener(event->node.device, event->node.node,
760 					sDriverWatcher);
761 				break;
762 		}
763 
764 		delete event;
765 	}
766 
767 	// Reload updated drivers
768 
769 	RecursiveLocker locker(sLock);
770 
771 	DriverTable::Iterator iterator(sDriverHash);
772 	while (iterator.HasNext()) {
773 		legacy_driver* driver = iterator.Next();
774 
775 		if (!driver->binary_updated || driver->devices_used != 0)
776 			continue;
777 
778 		// try to reload the driver
779 		reload_driver(driver);
780 	}
781 
782 	locker.Unlock();
783 }
784 
785 
786 //	#pragma mark - DriverWatcher
787 
788 
789 DriverWatcher::DriverWatcher()
790 {
791 }
792 
793 
794 DriverWatcher::~DriverWatcher()
795 {
796 }
797 
798 
799 void
800 DriverWatcher::EventOccurred(NotificationService& service,
801 	const KMessage* event)
802 {
803 	int32 opcode = event->GetInt32("opcode", -1);
804 	if (opcode != B_STAT_CHANGED
805 		|| (event->GetInt32("fields", 0) & B_STAT_MODIFICATION_TIME) == 0)
806 		return;
807 
808 	RecursiveLocker locker(sLock);
809 
810 	legacy_driver* driver = find_driver(event->GetInt32("device", -1),
811 		event->GetInt64("node", 0));
812 	if (driver == NULL)
813 		return;
814 
815 	driver->binary_updated = true;
816 
817 	if (driver->devices_used == 0) {
818 		// trigger a reload of the driver
819 		atomic_add(&sDriverEventsPending, 1);
820 	} else {
821 		// driver is in use right now
822 		dprintf("devfs: changed driver \"%s\" is still in use\n", driver->name);
823 	}
824 }
825 
826 
827 static void
828 dump_driver(legacy_driver* driver)
829 {
830 	kprintf("DEVFS DRIVER: %p\n", driver);
831 	kprintf(" name:           %s\n", driver->name);
832 	kprintf(" path:           %s\n", driver->path);
833 	kprintf(" image:          %" B_PRId32 "\n", driver->image);
834 	kprintf(" device:         %" B_PRIdDEV "\n", driver->device);
835 	kprintf(" node:           %" B_PRIdINO "\n", driver->node);
836 	kprintf(" last modified:  %" B_PRIdTIME ".%ld\n", driver->last_modified.tv_sec,
837 		driver->last_modified.tv_nsec);
838 	kprintf(" devs used:      %" B_PRIu32 "\n", driver->devices_used);
839 	kprintf(" devs published: %" B_PRId32 "\n", driver->devices.Count());
840 	kprintf(" binary updated: %d\n", driver->binary_updated);
841 	kprintf(" priority:       %" B_PRId32 "\n", driver->priority);
842 	kprintf(" api version:    %" B_PRId32 "\n", driver->api_version);
843 	kprintf(" hooks:          find_device %p, publish_devices %p\n"
844 		"                 uninit_driver %p, uninit_hardware %p\n",
845 		driver->find_device, driver->publish_devices, driver->uninit_driver,
846 		driver->uninit_hardware);
847 }
848 
849 
850 static int
851 dump_device(int argc, char** argv)
852 {
853 	if (argc < 2 || !strcmp(argv[1], "--help")) {
854 		kprintf("usage: %s [device]\n", argv[0]);
855 		return 0;
856 	}
857 
858 	LegacyDevice* device = (LegacyDevice*)parse_expression(argv[1]);
859 
860 	kprintf("LEGACY DEVICE: %p\n", device);
861 	kprintf(" path:     %s\n", device->Path());
862 	kprintf(" hooks:    %p\n", device->Hooks());
863 	device_hooks* hooks = device->Hooks();
864 	kprintf("  close()     %p\n", hooks->close);
865 	kprintf("  free()      %p\n", hooks->free);
866 	kprintf("  control()   %p\n", hooks->control);
867 	kprintf("  read()      %p\n", hooks->read);
868 	kprintf("  write()     %p\n", hooks->write);
869 	kprintf("  select()    %p\n", hooks->select);
870 	kprintf("  deselect()  %p\n", hooks->deselect);
871 	dump_driver(device->Driver());
872 
873 	return 0;
874 }
875 
876 
877 static int
878 dump_driver(int argc, char** argv)
879 {
880 	if (argc < 2) {
881 		// print list of all drivers
882 		kprintf("address    image used publ.   pri name\n");
883 		DriverTable::Iterator iterator(sDriverHash);
884 		while (iterator.HasNext()) {
885 			legacy_driver* driver = iterator.Next();
886 
887 			kprintf("%p  %5" B_PRId32 " %3" B_PRIu32 " %5" B_PRId32 " %c "
888 				"%3" B_PRId32 " %s\n", driver,
889 				driver->image < 0 ? -1 : driver->image,
890 				driver->devices_used, driver->devices.Count(),
891 				driver->binary_updated ? 'U' : ' ', driver->priority,
892 				driver->name);
893 		}
894 
895 		return 0;
896 	}
897 
898 	if (!strcmp(argv[1], "--help")) {
899 		kprintf("usage: %s [name]\n", argv[0]);
900 		return 0;
901 	}
902 
903 	legacy_driver* driver = sDriverHash->Lookup(argv[1]);
904 	if (driver == NULL) {
905 		kprintf("Driver named \"%s\" not found.\n", argv[1]);
906 		return 0;
907 	}
908 
909 	dump_driver(driver);
910 	return 0;
911 }
912 
913 
914 //	#pragma mark -
915 
916 
917 DirectoryIterator::DirectoryIterator(const char* path, const char* subPath,
918 		bool recursive)
919 	:
920 	fDirectory(NULL),
921 	fBasePath(NULL),
922 	fCurrentName(NULL)
923 {
924 	SetTo(path, subPath, recursive);
925 }
926 
927 
928 DirectoryIterator::~DirectoryIterator()
929 {
930 	Unset();
931 }
932 
933 
934 void
935 DirectoryIterator::SetTo(const char* path, const char* subPath, bool recursive)
936 {
937 	Unset();
938 	fRecursive = recursive;
939 
940 	if (path == NULL) {
941 		// add default paths
942 		KPath pathBuffer;
943 
944 		bool disableUserAddOns = get_safemode_boolean(
945 			B_SAFEMODE_DISABLE_USER_ADD_ONS, false);
946 
947 		for (uint32 i = 0; i < sizeof(kDriverPaths) / sizeof(kDriverPaths[0]); i++) {
948 			if (i < 3 && disableUserAddOns)
949 				continue;
950 
951 			if (__find_directory(kDriverPaths[i], gBootDevice, true,
952 					pathBuffer.LockBuffer(), pathBuffer.BufferSize()) == B_OK) {
953 				pathBuffer.UnlockBuffer();
954 				pathBuffer.Append("kernel");
955 				AddPath(pathBuffer.Path(), subPath);
956 			} else
957 				pathBuffer.UnlockBuffer();
958 		}
959 	} else
960 		AddPath(path, subPath);
961 }
962 
963 
964 status_t
965 DirectoryIterator::GetNext(KPath& path, struct stat& stat)
966 {
967 next_directory:
968 	while (fDirectory == NULL) {
969 		delete fBasePath;
970 		fBasePath = NULL;
971 
972 		if (!fPaths.Pop(&fBasePath))
973 			return B_ENTRY_NOT_FOUND;
974 
975 		fDirectory = opendir(fBasePath->Path());
976 	}
977 
978 next_entry:
979 	struct dirent* dirent = readdir(fDirectory);
980 	if (dirent == NULL) {
981 		// get over to next directory on the stack
982 		closedir(fDirectory);
983 		fDirectory = NULL;
984 
985 		goto next_directory;
986 	}
987 
988 	if (!strcmp(dirent->d_name, "..") || !strcmp(dirent->d_name, "."))
989 		goto next_entry;
990 
991 	fCurrentName = dirent->d_name;
992 
993 	path.SetTo(fBasePath->Path());
994 	path.Append(fCurrentName);
995 
996 	if (::stat(path.Path(), &stat) != 0)
997 		goto next_entry;
998 
999 	if (S_ISDIR(stat.st_mode) && fRecursive) {
1000 		KPath* nextPath = new(nothrow) KPath(path);
1001 		if (!nextPath)
1002 			return B_NO_MEMORY;
1003 		if (fPaths.Push(nextPath) != B_OK)
1004 			return B_NO_MEMORY;
1005 
1006 		goto next_entry;
1007 	}
1008 
1009 	return B_OK;
1010 }
1011 
1012 
1013 void
1014 DirectoryIterator::Unset()
1015 {
1016 	if (fDirectory != NULL) {
1017 		closedir(fDirectory);
1018 		fDirectory = NULL;
1019 	}
1020 
1021 	delete fBasePath;
1022 	fBasePath = NULL;
1023 
1024 	KPath* path;
1025 	while (fPaths.Pop(&path))
1026 		delete path;
1027 }
1028 
1029 
1030 void
1031 DirectoryIterator::AddPath(const char* basePath, const char* subPath)
1032 {
1033 	KPath* path = new(nothrow) KPath(basePath);
1034 	if (!path)
1035 		panic("out of memory");
1036 	if (subPath != NULL)
1037 		path->Append(subPath);
1038 
1039 	fPaths.Push(path);
1040 }
1041 
1042 
1043 //	#pragma mark -
1044 
1045 
1046 DirectoryWatcher::DirectoryWatcher()
1047 {
1048 }
1049 
1050 
1051 DirectoryWatcher::~DirectoryWatcher()
1052 {
1053 }
1054 
1055 
1056 void
1057 DirectoryWatcher::EventOccurred(NotificationService& service,
1058 	const KMessage* event)
1059 {
1060 	int32 opcode = event->GetInt32("opcode", -1);
1061 	dev_t device = event->GetInt32("device", -1);
1062 	ino_t directory = event->GetInt64("directory", -1);
1063 	const char* name = event->GetString("name", NULL);
1064 
1065 	if (opcode == B_ENTRY_MOVED) {
1066 		// Determine whether it's a move within, out of, or into one
1067 		// of our watched directories.
1068 		ino_t from = event->GetInt64("from directory", -1);
1069 		ino_t to = event->GetInt64("to directory", -1);
1070 		if (sDirectoryNodeHash.Lookup(&from) == NULL) {
1071 			directory = to;
1072 			opcode = B_ENTRY_CREATED;
1073 		} else if (sDirectoryNodeHash.Lookup(&to) == NULL) {
1074 			directory = from;
1075 			opcode = B_ENTRY_REMOVED;
1076 		} else {
1077 			// Move within, don't do anything for now
1078 			// TODO: adjust driver priority if necessary
1079 			return;
1080 		}
1081 	}
1082 
1083 	KPath path(B_PATH_NAME_LENGTH + 1);
1084 	if (path.InitCheck() != B_OK || vfs_entry_ref_to_path(device, directory,
1085 			name, true, path.LockBuffer(), path.BufferSize()) != B_OK)
1086 		return;
1087 
1088 	path.UnlockBuffer();
1089 
1090 	dprintf("driver \"%s\" %s\n", path.Leaf(),
1091 		opcode == B_ENTRY_CREATED ? "added" : "removed");
1092 
1093 	driver_event* driverEvent = new(std::nothrow) driver_event(
1094 		opcode == B_ENTRY_CREATED ? kAddDriver : kRemoveDriver);
1095 	if (driverEvent == NULL)
1096 		return;
1097 
1098 	strlcpy(driverEvent->path, path.Path(), sizeof(driverEvent->path));
1099 
1100 	MutexLocker _(sDriverEventsLock);
1101 	sDriverEvents.Add(driverEvent);
1102 	atomic_add(&sDriverEventsPending, 1);
1103 }
1104 
1105 
1106 //	#pragma mark -
1107 
1108 
1109 static void
1110 start_watching(const char* base, const char* sub)
1111 {
1112 	KPath path(base);
1113 	path.Append(sub);
1114 
1115 	// TODO: create missing directories?
1116 	struct stat stat;
1117 	if (::stat(path.Path(), &stat) != 0)
1118 		return;
1119 
1120 	add_node_listener(stat.st_dev, stat.st_ino, B_WATCH_DIRECTORY,
1121 		sDirectoryWatcher);
1122 
1123 	directory_node_entry* entry = new(std::nothrow) directory_node_entry;
1124 	if (entry != NULL) {
1125 		entry->node = stat.st_ino;
1126 		sDirectoryNodeHash.Insert(entry);
1127 	}
1128 }
1129 
1130 
1131 static struct driver_entry*
1132 new_driver_entry(const char* path, dev_t device, ino_t node)
1133 {
1134 	driver_entry* entry = (driver_entry*)malloc(sizeof(driver_entry));
1135 	if (entry == NULL)
1136 		return NULL;
1137 
1138 	entry->path = strdup(path);
1139 	if (entry->path == NULL) {
1140 		free(entry);
1141 		return NULL;
1142 	}
1143 
1144 	entry->device = device;
1145 	entry->node = node;
1146 	entry->busses = 0;
1147 	return entry;
1148 }
1149 
1150 
1151 /*!	Iterates over the given list and tries to load all drivers in that list.
1152 	The list is emptied and freed during the traversal.
1153 */
1154 static status_t
1155 try_drivers(DriverEntryList& list)
1156 {
1157 	while (true) {
1158 		driver_entry* entry = list.RemoveHead();
1159 		if (entry == NULL)
1160 			break;
1161 
1162 		image_id image = load_kernel_add_on(entry->path);
1163 		if (image >= 0) {
1164 			// check if it's an old-style driver
1165 			if (legacy_driver_add(entry->path) == B_OK) {
1166 				// we have a driver
1167 				dprintf("loaded driver %s\n", entry->path);
1168 			}
1169 
1170 			unload_kernel_add_on(image);
1171 		}
1172 
1173 		free(entry->path);
1174 		free(entry);
1175 	}
1176 
1177 	return B_OK;
1178 }
1179 
1180 
1181 static status_t
1182 probe_for_drivers(const char* type)
1183 {
1184 	TRACE(("probe_for_drivers(type = %s)\n", type));
1185 
1186 	if (gBootDevice < 0)
1187 		return B_OK;
1188 
1189 	DriverEntryList drivers;
1190 
1191 	// build list of potential drivers for that type
1192 
1193 	DirectoryIterator iterator(NULL, type, false);
1194 	struct stat stat;
1195 	KPath path;
1196 
1197 	while (iterator.GetNext(path, stat) == B_OK) {
1198 		if (S_ISDIR(stat.st_mode)) {
1199 			add_node_listener(stat.st_dev, stat.st_ino, B_WATCH_DIRECTORY,
1200 				sDirectoryWatcher);
1201 
1202 			directory_node_entry* entry
1203 				= new(std::nothrow) directory_node_entry;
1204 			if (entry != NULL) {
1205 				entry->node = stat.st_ino;
1206 				sDirectoryNodeHash.Insert(entry);
1207 			}
1208 
1209 			// We need to make sure that drivers in ie. "audio/raw/" can
1210 			// be found as well - therefore, we must make sure that "audio"
1211 			// exists on /dev.
1212 
1213 			size_t length = strlen("drivers/dev");
1214 			if (strncmp(type, "drivers/dev", length))
1215 				continue;
1216 
1217 			path.SetTo(type);
1218 			path.Append(iterator.CurrentName());
1219 			devfs_publish_directory(path.Path() + length + 1);
1220 			continue;
1221 		}
1222 
1223 		driver_entry* entry = new_driver_entry(path.Path(), stat.st_dev,
1224 			stat.st_ino);
1225 		if (entry == NULL)
1226 			return B_NO_MEMORY;
1227 
1228 		TRACE(("found potential driver: %s\n", path.Path()));
1229 		drivers.Add(entry);
1230 	}
1231 
1232 	if (drivers.IsEmpty())
1233 		return B_OK;
1234 
1235 	// ToDo: do something with the remaining drivers... :)
1236 	try_drivers(drivers);
1237 	return B_OK;
1238 }
1239 
1240 
1241 //	#pragma mark - LegacyDevice
1242 
1243 
1244 LegacyDevice::LegacyDevice(legacy_driver* driver, const char* path,
1245 		device_hooks* hooks)
1246 	:
1247 	fDriver(driver),
1248 	fRepublished(true),
1249 	fRemovedFromParent(false)
1250 {
1251 	fDeviceModule = (device_module_info*)malloc(sizeof(device_module_info));
1252 	if (fDeviceModule != NULL)
1253 		memset(fDeviceModule, 0, sizeof(device_module_info));
1254 
1255 	fDeviceData = this;
1256 	fPath = strdup(path);
1257 
1258 	SetHooks(hooks);
1259 }
1260 
1261 
1262 LegacyDevice::~LegacyDevice()
1263 {
1264 	free(fDeviceModule);
1265 	free((char*)fPath);
1266 }
1267 
1268 
1269 status_t
1270 LegacyDevice::InitCheck() const
1271 {
1272 	return fDeviceModule != NULL && fPath != NULL ? B_OK : B_NO_MEMORY;
1273 }
1274 
1275 
1276 status_t
1277 LegacyDevice::InitDevice()
1278 {
1279 	RecursiveLocker _(sLock);
1280 
1281 	if (fInitialized++ > 0)
1282 		return B_OK;
1283 
1284 	if (fDriver != NULL && fDriver->devices_used == 0
1285 		&& (fDriver->image < 0 || fDriver->binary_updated)) {
1286 		status_t status = reload_driver(fDriver);
1287 		if (status < B_OK)
1288 			return status;
1289 	}
1290 
1291 	if (fDriver != NULL)
1292 		fDriver->devices_used++;
1293 
1294 	return B_OK;
1295 }
1296 
1297 
1298 void
1299 LegacyDevice::UninitDevice()
1300 {
1301 	RecursiveLocker _(sLock);
1302 
1303 	if (fInitialized-- > 1)
1304 		return;
1305 
1306 	if (fDriver != NULL) {
1307 		if (--fDriver->devices_used == 0 && fDriver->devices.IsEmpty())
1308 			unload_driver(fDriver);
1309 		fDriver = NULL;
1310 	}
1311 }
1312 
1313 
1314 void
1315 LegacyDevice::Removed()
1316 {
1317 	RecursiveLocker _(sLock);
1318 
1319 	if (!fRemovedFromParent && fDriver != NULL)
1320 		fDriver->devices.Remove(this);
1321 
1322 	delete this;
1323 }
1324 
1325 
1326 void
1327 LegacyDevice::SetHooks(device_hooks* hooks)
1328 {
1329 	// TODO: setup compatibility layer!
1330 	fHooks = hooks;
1331 
1332 	fDeviceModule->close = hooks->close;
1333 	fDeviceModule->free = hooks->free;
1334 	fDeviceModule->control = hooks->control;
1335 	fDeviceModule->read = hooks->read;
1336 	fDeviceModule->write = hooks->write;
1337 
1338 	if (fDriver == NULL || fDriver->api_version >= 2) {
1339 		// According to Be newsletter, vol II, issue 36,
1340 		// version 2 added readv/writev, which we don't support, but also
1341 		// select/deselect.
1342 		if (hooks->select != NULL) {
1343 			// Note we set the module's select to a non-null value to indicate
1344 			// that we have select. HasSelect() will therefore return the
1345 			// correct answer. As Select() is virtual our compatibility
1346 			// version below is going to be called though, that redirects to
1347 			// the proper select hook, so it is ok to set it to an invalid
1348 			// address here.
1349 			fDeviceModule->select = (status_t (*)(void*, uint8, selectsync*))~0;
1350 		}
1351 
1352 		fDeviceModule->deselect = hooks->deselect;
1353 	}
1354 }
1355 
1356 
1357 status_t
1358 LegacyDevice::Open(const char* path, int openMode, void** _cookie)
1359 {
1360 	return Hooks()->open(path, openMode, _cookie);
1361 }
1362 
1363 
1364 status_t
1365 LegacyDevice::Select(void* cookie, uint8 event, selectsync* sync)
1366 {
1367 	return Hooks()->select(cookie, event, 0, sync);
1368 }
1369 
1370 
1371 //	#pragma mark - kernel private API
1372 
1373 
1374 extern "C" void
1375 legacy_driver_add_preloaded(kernel_args* args)
1376 {
1377 	// NOTE: This function does not exit in case of error, since it
1378 	// needs to unload the images then. Also the return code of
1379 	// the path operations is kept separate from the add_driver()
1380 	// success, so that even if add_driver() fails for one driver, it
1381 	// is still tried for the other drivers.
1382 	// NOTE: The initialization success of the path objects is implicitely
1383 	// checked by the immediately following functions.
1384 	KPath basePath;
1385 	status_t status = __find_directory(B_SYSTEM_ADDONS_DIRECTORY,
1386 		gBootDevice, false, basePath.LockBuffer(), basePath.BufferSize());
1387 	if (status != B_OK) {
1388 		dprintf("legacy_driver_add_preloaded: find_directory() failed: "
1389 			"%s\n", strerror(status));
1390 	}
1391 	basePath.UnlockBuffer();
1392 	if (status == B_OK)
1393 		status = basePath.Append("kernel");
1394 	if (status != B_OK) {
1395 		dprintf("legacy_driver_add_preloaded: constructing base driver "
1396 			"path failed: %s\n", strerror(status));
1397 		return;
1398 	}
1399 
1400 	struct preloaded_image* image;
1401 	for (image = args->preloaded_images; image != NULL; image = image->next) {
1402 		if (image->is_module || image->id < 0)
1403 			continue;
1404 
1405 		KPath imagePath(basePath);
1406 		status = imagePath.Append(image->name);
1407 
1408 		// try to add the driver
1409 		TRACE(("legacy_driver_add_preloaded: adding driver %s\n",
1410 			imagePath.Path()));
1411 
1412 		if (status == B_OK)
1413 			status = add_driver(imagePath.Path(), image->id);
1414 		if (status != B_OK) {
1415 			dprintf("legacy_driver_add_preloaded: Failed to add \"%s\": %s\n",
1416 				(char*)image->name, strerror(status));
1417 			unload_kernel_add_on(image->id);
1418 		}
1419 	}
1420 }
1421 
1422 
1423 extern "C" status_t
1424 legacy_driver_add(const char* path)
1425 {
1426 	return add_driver(path, -1);
1427 }
1428 
1429 
1430 extern "C" status_t
1431 legacy_driver_publish(const char* path, device_hooks* hooks)
1432 {
1433 	// we don't have a driver, just publish the hooks
1434 	LegacyDevice* device = new(std::nothrow) LegacyDevice(NULL, path, hooks);
1435 	if (device == NULL)
1436 		return B_NO_MEMORY;
1437 
1438 	status_t status = device->InitCheck();
1439 	if (status == B_OK)
1440 		status = devfs_publish_device(path, device);
1441 
1442 	if (status != B_OK)
1443 		delete device;
1444 
1445 	return status;
1446 }
1447 
1448 
1449 extern "C" status_t
1450 legacy_driver_rescan(const char* driverName)
1451 {
1452 	RecursiveLocker locker(sLock);
1453 
1454 	legacy_driver* driver = sDriverHash->Lookup(driverName);
1455 	if (driver == NULL)
1456 		return B_ENTRY_NOT_FOUND;
1457 
1458 	// Republish the driver's entries
1459 	return republish_driver(driver);
1460 }
1461 
1462 
1463 extern "C" status_t
1464 legacy_driver_probe(const char* subPath)
1465 {
1466 	TRACE(("legacy_driver_probe(type = %s)\n", subPath));
1467 
1468 	char devicePath[64];
1469 	snprintf(devicePath, sizeof(devicePath), "drivers/dev%s%s",
1470 		subPath[0] ? "/" : "", subPath);
1471 
1472 	if (!sWatching && gBootDevice > 0) {
1473 		// We're probing the actual boot volume for the first time,
1474 		// let's watch its driver directories for changes
1475 		KPath path;
1476 
1477 		new(&sDirectoryWatcher) DirectoryWatcher;
1478 
1479 		bool disableUserAddOns = get_safemode_boolean(
1480 			B_SAFEMODE_DISABLE_USER_ADD_ONS, false);
1481 
1482 		for (uint32 i = 0; i < sizeof(kDriverPaths) / sizeof(kDriverPaths[0]); i++) {
1483 			if (i < 3 && disableUserAddOns)
1484 				continue;
1485 
1486 			if (__find_directory(kDriverPaths[i], gBootDevice, true,
1487 					path.LockBuffer(), path.BufferSize()) == B_OK) {
1488 				path.UnlockBuffer();
1489 				path.Append("kernel/drivers");
1490 
1491 				start_watching(path.Path(), "bin");
1492 			} else
1493 				path.UnlockBuffer();
1494 		}
1495 
1496 		sWatching = true;
1497 	}
1498 
1499 	return probe_for_drivers(devicePath);
1500 }
1501 
1502 
1503 extern "C" status_t
1504 legacy_driver_init(void)
1505 {
1506 	sDriverHash = new DriverTable();
1507 	if (sDriverHash == NULL || sDriverHash->Init(DRIVER_HASH_SIZE) != B_OK)
1508 		return B_NO_MEMORY;
1509 
1510 	recursive_lock_init(&sLock, "legacy driver");
1511 
1512 	new(&sDriverWatcher) DriverWatcher;
1513 	new(&sDriverEvents) DriverEventList;
1514 
1515 	register_kernel_daemon(&handle_driver_events, NULL, 10);
1516 		// once every second
1517 
1518 	add_debugger_command("legacy_driver", &dump_driver,
1519 		"info about a legacy driver entry");
1520 	add_debugger_command("legacy_device", &dump_device,
1521 		"info about a legacy device");
1522 
1523 	return B_OK;
1524 }
1525