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