/* * Copyright 2002-2011, Axel Dörfler, axeld@pinc-software.de. * Distributed under the terms of the MIT License. */ #include "legacy_drivers.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AbstractModuleDevice.h" #include "devfs_private.h" //#define TRACE_LEGACY_DRIVERS #ifdef TRACE_LEGACY_DRIVERS # define TRACE(x) dprintf x #else # define TRACE(x) #endif #define DRIVER_HASH_SIZE 16 namespace { struct legacy_driver; class LegacyDevice : public AbstractModuleDevice, public DoublyLinkedListLinkImpl { public: LegacyDevice(legacy_driver* driver, const char* path, device_hooks* hooks); virtual ~LegacyDevice(); status_t InitCheck() const; virtual status_t InitDevice(); virtual void UninitDevice(); virtual void Removed(); void SetHooks(device_hooks* hooks); legacy_driver* Driver() const { return fDriver; } const char* Path() const { return fPath; } device_hooks* Hooks() const { return fHooks; } virtual status_t Open(const char* path, int openMode, void** _cookie); virtual status_t Select(void* cookie, uint8 event, selectsync* sync); virtual status_t Control(void* cookie, int32 op, void* buffer, size_t length); bool Republished() const { return fRepublished; } void SetRepublished(bool republished) { fRepublished = republished; } void SetRemovedFromParent(bool removed) { fRemovedFromParent = removed; } private: legacy_driver* fDriver; const char* fPath; device_hooks* fHooks; bool fRepublished; bool fRemovedFromParent; }; typedef DoublyLinkedList DeviceList; struct legacy_driver { legacy_driver* next; const char* path; const char* name; dev_t device; ino_t node; timespec last_modified; image_id image; uint32 devices_used; bool binary_updated; int32 priority; DeviceList devices; // driver image information int32 api_version; device_hooks* (*find_device)(const char *); const char** (*publish_devices)(void); status_t (*uninit_driver)(void); status_t (*uninit_hardware)(void); }; enum driver_event_type { kAddDriver, kRemoveDriver, kAddWatcher, kRemoveWatcher }; struct driver_event : DoublyLinkedListLinkImpl { driver_event(driver_event_type _type) : type(_type) {} struct ref { dev_t device; ino_t node; }; driver_event_type type; union { char path[B_PATH_NAME_LENGTH]; ref node; }; }; typedef DoublyLinkedList DriverEventList; struct driver_entry : DoublyLinkedListLinkImpl { char* path; dev_t device; ino_t node; int32 busses; }; typedef DoublyLinkedList DriverEntryList; struct node_entry : DoublyLinkedListLinkImpl { }; typedef DoublyLinkedList NodeList; struct directory_node_entry { directory_node_entry* hash_link; ino_t node; }; struct DirectoryNodeHashDefinition { typedef ino_t* KeyType; typedef directory_node_entry ValueType; size_t HashKey(ino_t* key) const { return _Hash(*key); } size_t Hash(directory_node_entry* entry) const { return _Hash(entry->node); } bool Compare(ino_t* key, directory_node_entry* entry) const { return *key == entry->node; } directory_node_entry*& GetLink(directory_node_entry* entry) const { return entry->hash_link; } uint32 _Hash(ino_t node) const { return (uint32)(node >> 32) + (uint32)node; } }; typedef BOpenHashTable DirectoryNodeHash; class DirectoryIterator { public: DirectoryIterator(const char *path, const char *subPath = NULL, bool recursive = false); ~DirectoryIterator(); void SetTo(const char *path, const char *subPath = NULL, bool recursive = false); status_t GetNext(KPath &path, struct stat &stat); const char* CurrentName() const { return fCurrentName; } void Unset(); void AddPath(const char *path, const char *subPath = NULL); private: Stack fPaths; bool fRecursive; DIR* fDirectory; KPath* fBasePath; const char* fCurrentName; }; class DirectoryWatcher : public NotificationListener { public: DirectoryWatcher(); virtual ~DirectoryWatcher(); virtual void EventOccurred(NotificationService& service, const KMessage* event); }; class DriverWatcher : public NotificationListener { public: DriverWatcher(); virtual ~DriverWatcher(); virtual void EventOccurred(NotificationService& service, const KMessage* event); }; struct DriverHash { typedef const char* KeyType; typedef legacy_driver ValueType; size_t HashKey(KeyType key) const { return hash_hash_string(key); } size_t Hash(ValueType* driver) const { return HashKey(driver->name); } bool Compare(KeyType key, ValueType* driver) const { return strcmp(driver->name, key) == 0; } ValueType*& GetLink(ValueType* value) const { return value->next; } }; typedef BOpenHashTable DriverTable; } // unnamed namespace static status_t unload_driver(legacy_driver *driver); static status_t load_driver(legacy_driver *driver); static const directory_which kDriverPaths[] = { B_USER_NONPACKAGED_ADDONS_DIRECTORY, B_USER_ADDONS_DIRECTORY, B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY, B_SYSTEM_ADDONS_DIRECTORY }; static DriverWatcher sDriverWatcher; static int32 sDriverEventsPending; static DriverEventList sDriverEvents; static mutex sDriverEventsLock = MUTEX_INITIALIZER("driver events"); // inner lock, protects the sDriverEvents list only static DirectoryWatcher sDirectoryWatcher; static DirectoryNodeHash sDirectoryNodeHash; static recursive_lock sLock; static bool sWatching; static DriverTable* sDriverHash; // #pragma mark - driver private /*! Collects all published devices of a driver, compares them to what the driver would publish now, and then publishes/unpublishes the devices as needed. If the driver does not publish any devices anymore, it is unloaded. */ static status_t republish_driver(legacy_driver* driver) { if (driver->image < 0) { // The driver is not yet loaded - go through the normal load procedure return load_driver(driver); } // mark all devices DeviceList::Iterator iterator = driver->devices.GetIterator(); while (LegacyDevice* device = iterator.Next()) { device->SetRepublished(false); } // now ask the driver for it's currently published devices const char** devicePaths = driver->publish_devices(); int32 exported = 0; for (; devicePaths != NULL && devicePaths[0]; devicePaths++) { LegacyDevice* device; iterator = driver->devices.GetIterator(); while ((device = iterator.Next()) != NULL) { if (!strncmp(device->Path(), devicePaths[0], B_PATH_NAME_LENGTH)) { // mark device as republished device->SetRepublished(true); exported++; break; } } device_hooks* hooks = driver->find_device(devicePaths[0]); if (hooks == NULL) continue; if (device != NULL) { // update hooks device->SetHooks(hooks); continue; } // the device was not present before -> publish it now TRACE(("devfs: publishing new device \"%s\"\n", devicePaths[0])); device = new(std::nothrow) LegacyDevice(driver, devicePaths[0], hooks); if (device != NULL && device->InitCheck() == B_OK && devfs_publish_device(devicePaths[0], device) == B_OK) { driver->devices.Add(device); exported++; } else delete device; } // remove all devices that weren't republished iterator = driver->devices.GetIterator(); while (LegacyDevice* device = iterator.Next()) { if (device->Republished()) continue; TRACE(("devfs: unpublishing no more present \"%s\"\n", device->Path())); iterator.Remove(); device->SetRemovedFromParent(true); devfs_unpublish_device(device, true); } if (exported == 0 && driver->devices_used == 0 && gBootDevice >= 0) { TRACE(("devfs: driver \"%s\" does not publish any more nodes and is " "unloaded\n", driver->path)); unload_driver(driver); } return B_OK; } static status_t load_driver(legacy_driver* driver) { status_t (*init_hardware)(void); status_t (*init_driver)(void); status_t status; driver->binary_updated = false; // load the module image_id image = driver->image; if (image < 0) { image = load_kernel_add_on(driver->path); if (image < 0) return image; } // For a valid device driver the following exports are required int32* apiVersion; if (get_image_symbol(image, "api_version", B_SYMBOL_TYPE_DATA, (void**)&apiVersion) == B_OK) { #if B_CUR_DRIVER_API_VERSION != 2 // just in case someone decides to bump up the api version #error Add checks here for new vs old api version! #endif if (*apiVersion > B_CUR_DRIVER_API_VERSION) { dprintf("devfs: \"%s\" api_version %" B_PRId32 " not handled\n", driver->name, *apiVersion); status = B_BAD_VALUE; goto error1; } if (*apiVersion < 1) { dprintf("devfs: \"%s\" api_version invalid\n", driver->name); status = B_BAD_VALUE; goto error1; } driver->api_version = *apiVersion; } else dprintf("devfs: \"%s\" api_version missing\n", driver->name); if (get_image_symbol(image, "publish_devices", B_SYMBOL_TYPE_TEXT, (void**)&driver->publish_devices) != B_OK || get_image_symbol(image, "find_device", B_SYMBOL_TYPE_TEXT, (void**)&driver->find_device) != B_OK) { dprintf("devfs: \"%s\" mandatory driver symbol(s) missing!\n", driver->name); status = B_BAD_VALUE; goto error1; } // Init the driver if (get_image_symbol(image, "init_hardware", B_SYMBOL_TYPE_TEXT, (void**)&init_hardware) == B_OK && (status = init_hardware()) != B_OK) { TRACE(("%s: init_hardware() failed: %s\n", driver->name, strerror(status))); status = ENXIO; goto error1; } if (get_image_symbol(image, "init_driver", B_SYMBOL_TYPE_TEXT, (void**)&init_driver) == B_OK && (status = init_driver()) != B_OK) { TRACE(("%s: init_driver() failed: %s\n", driver->name, strerror(status))); status = ENXIO; goto error2; } // resolve and cache those for the driver unload code if (get_image_symbol(image, "uninit_driver", B_SYMBOL_TYPE_TEXT, (void**)&driver->uninit_driver) != B_OK) driver->uninit_driver = NULL; if (get_image_symbol(image, "uninit_hardware", B_SYMBOL_TYPE_TEXT, (void**)&driver->uninit_hardware) != B_OK) driver->uninit_hardware = NULL; // The driver has successfully been initialized, now we can // finally publish its device entries driver->image = image; return republish_driver(driver); error2: if (driver->uninit_hardware) driver->uninit_hardware(); error1: if (driver->image < 0) { unload_kernel_add_on(image); driver->image = status; } return status; } static status_t unload_driver(legacy_driver* driver) { if (driver->image < 0) { // driver is not currently loaded return B_NO_INIT; } if (driver->uninit_driver) driver->uninit_driver(); if (driver->uninit_hardware) driver->uninit_hardware(); unload_kernel_add_on(driver->image); driver->image = -1; driver->binary_updated = false; driver->find_device = NULL; driver->publish_devices = NULL; driver->uninit_driver = NULL; driver->uninit_hardware = NULL; return B_OK; } /*! Unpublishes all devices belonging to the \a driver. */ static void unpublish_driver(legacy_driver* driver) { while (LegacyDevice* device = driver->devices.RemoveHead()) { device->SetRemovedFromParent(true); devfs_unpublish_device(device, true); } } static void change_driver_watcher(dev_t device, ino_t node, bool add) { if (device == -1) return; driver_event* event = new (std::nothrow) driver_event( add ? kAddWatcher : kRemoveWatcher); if (event == NULL) return; event->node.device = device; event->node.node = node; MutexLocker _(sDriverEventsLock); sDriverEvents.Add(event); atomic_add(&sDriverEventsPending, 1); } static int32 get_priority(const char* path) { // TODO: would it be better to initialize a static structure here // using find_directory()? const directory_which whichPath[] = { B_SYSTEM_DIRECTORY, B_SYSTEM_NONPACKAGED_DIRECTORY, B_USER_DIRECTORY }; KPath pathBuffer; for (uint32 index = 0; index < sizeof(whichPath) / sizeof(whichPath[0]); index++) { if (__find_directory(whichPath[index], gBootDevice, false, pathBuffer.LockBuffer(), pathBuffer.BufferSize()) == B_OK) { pathBuffer.UnlockBuffer(); if (!strncmp(pathBuffer.Path(), path, pathBuffer.BufferSize())) return index; } else pathBuffer.UnlockBuffer(); } return -1; } static const char* get_leaf(const char* path) { const char* name = strrchr(path, '/'); if (name == NULL) return path; return name + 1; } static legacy_driver* find_driver(dev_t device, ino_t node) { DriverTable::Iterator iterator(sDriverHash); while (iterator.HasNext()) { legacy_driver* driver = iterator.Next(); if (driver->device == device && driver->node == node) return driver; } return NULL; } static status_t add_driver(const char* path, image_id image) { // Check if we already know this driver struct stat stat; if (image >= 0) { // The image ID should be a small number and hopefully the boot FS // doesn't use small negative values -- if it is inode based, we should // be relatively safe. stat.st_dev = -1; stat.st_ino = -1; } else { if (::stat(path, &stat) != 0) return errno; } int32 priority = get_priority(path); RecursiveLocker _(sLock); legacy_driver* driver = sDriverHash->Lookup(get_leaf(path)); if (driver != NULL) { // we know this driver if (strcmp(driver->path, path) != 0) { // TODO: do properly, but for now we just update the path if it // isn't the same anymore so rescanning of drivers will work in // case this driver was loaded so early that it has a boot module // path and not a proper driver path free((char*)driver->path); driver->path = strdup(path); driver->name = get_leaf(driver->path); driver->binary_updated = true; } // TODO: check if this driver is a different one and has precendence // (ie. common supersedes system). //dprintf("new driver has priority %ld, old %ld\n", priority, driver->priority); if (priority >= driver->priority) { driver->binary_updated = true; return B_OK; } // TODO: test for changes here and/or via node monitoring and reload // the driver if necessary if (driver->image < B_OK) return driver->image; return B_OK; } // we don't know this driver, create a new entry for it driver = (legacy_driver*)malloc(sizeof(legacy_driver)); if (driver == NULL) return B_NO_MEMORY; driver->path = strdup(path); if (driver->path == NULL) { free(driver); return B_NO_MEMORY; } driver->name = get_leaf(driver->path); driver->device = stat.st_dev; driver->node = stat.st_ino; driver->image = image; driver->last_modified = stat.st_mtim; driver->devices_used = 0; driver->binary_updated = false; driver->priority = priority; driver->api_version = 1; driver->find_device = NULL; driver->publish_devices = NULL; driver->uninit_driver = NULL; driver->uninit_hardware = NULL; new(&driver->devices) DeviceList; sDriverHash->Insert(driver); if (stat.st_dev > 0) change_driver_watcher(stat.st_dev, stat.st_ino, true); // Even if loading the driver fails - its entry will stay with us // so that we don't have to go through it again return load_driver(driver); } /*! This is no longer part of the public kernel API, so we just export the symbol */ extern "C" status_t load_driver_symbols(const char* driverName); status_t load_driver_symbols(const char* driverName) { // This is done globally for the whole kernel via the settings file. // We don't have to do anything here. return B_OK; } static status_t reload_driver(legacy_driver* driver) { dprintf("devfs: reload driver \"%s\" (%" B_PRIdDEV ", %" B_PRIdINO ")\n", driver->name, driver->device, driver->node); unload_driver(driver); struct stat stat; if (::stat(driver->path, &stat) == 0 && (stat.st_dev != driver->device || stat.st_ino != driver->node)) { // The driver file has been changed, so we need to update its listener change_driver_watcher(driver->device, driver->node, false); driver->device = stat.st_dev; driver->node = stat.st_ino; change_driver_watcher(driver->device, driver->node, true); } status_t status = load_driver(driver); if (status != B_OK) unpublish_driver(driver); return status; } static void handle_driver_events(void* /*_fs*/, int /*iteration*/) { if (atomic_and(&sDriverEventsPending, 0) == 0) return; // something happened, let's see what it was while (true) { MutexLocker eventLocker(sDriverEventsLock); driver_event* event = sDriverEvents.RemoveHead(); if (event == NULL) break; eventLocker.Unlock(); TRACE(("driver event %p, type %d\n", event, event->type)); switch (event->type) { case kAddDriver: { // Add new drivers RecursiveLocker locker(sLock); TRACE((" add driver %p\n", event->path)); legacy_driver* driver = sDriverHash->Lookup( get_leaf(event->path)); if (driver == NULL) legacy_driver_add(event->path); else if (get_priority(event->path) >= driver->priority) driver->binary_updated = true; break; } case kRemoveDriver: { // Mark removed drivers as updated RecursiveLocker locker(sLock); TRACE((" remove driver %p\n", event->path)); legacy_driver* driver = sDriverHash->Lookup( get_leaf(event->path)); if (driver != NULL && get_priority(event->path) >= driver->priority) driver->binary_updated = true; break; } case kAddWatcher: TRACE((" add watcher %ld:%lld\n", event->node.device, event->node.node)); add_node_listener(event->node.device, event->node.node, B_WATCH_STAT | B_WATCH_NAME, sDriverWatcher); break; case kRemoveWatcher: TRACE((" remove watcher %ld:%lld\n", event->node.device, event->node.node)); remove_node_listener(event->node.device, event->node.node, sDriverWatcher); break; } delete event; } // Reload updated drivers RecursiveLocker locker(sLock); DriverTable::Iterator iterator(sDriverHash); while (iterator.HasNext()) { legacy_driver* driver = iterator.Next(); if (!driver->binary_updated || driver->devices_used != 0) continue; // try to reload the driver reload_driver(driver); } locker.Unlock(); } // #pragma mark - DriverWatcher DriverWatcher::DriverWatcher() { } DriverWatcher::~DriverWatcher() { } void DriverWatcher::EventOccurred(NotificationService& service, const KMessage* event) { int32 opcode = event->GetInt32("opcode", -1); if (opcode != B_STAT_CHANGED || (event->GetInt32("fields", 0) & B_STAT_MODIFICATION_TIME) == 0) return; RecursiveLocker locker(sLock); legacy_driver* driver = find_driver(event->GetInt32("device", -1), event->GetInt64("node", 0)); if (driver == NULL) return; driver->binary_updated = true; if (driver->devices_used == 0) { // trigger a reload of the driver atomic_add(&sDriverEventsPending, 1); } else { // driver is in use right now dprintf("devfs: changed driver \"%s\" is still in use\n", driver->name); } } static void dump_driver(legacy_driver* driver) { kprintf("DEVFS DRIVER: %p\n", driver); kprintf(" name: %s\n", driver->name); kprintf(" path: %s\n", driver->path); kprintf(" image: %" B_PRId32 "\n", driver->image); kprintf(" device: %" B_PRIdDEV "\n", driver->device); kprintf(" node: %" B_PRIdINO "\n", driver->node); kprintf(" last modified: %" B_PRIdTIME ".%ld\n", driver->last_modified.tv_sec, driver->last_modified.tv_nsec); kprintf(" devs used: %" B_PRIu32 "\n", driver->devices_used); kprintf(" devs published: %" B_PRId32 "\n", driver->devices.Count()); kprintf(" binary updated: %d\n", driver->binary_updated); kprintf(" priority: %" B_PRId32 "\n", driver->priority); kprintf(" api version: %" B_PRId32 "\n", driver->api_version); kprintf(" hooks: find_device %p, publish_devices %p\n" " uninit_driver %p, uninit_hardware %p\n", driver->find_device, driver->publish_devices, driver->uninit_driver, driver->uninit_hardware); } static int dump_device(int argc, char** argv) { if (argc < 2 || !strcmp(argv[1], "--help")) { kprintf("usage: %s [device]\n", argv[0]); return 0; } LegacyDevice* device = (LegacyDevice*)parse_expression(argv[1]); kprintf("LEGACY DEVICE: %p\n", device); kprintf(" path: %s\n", device->Path()); kprintf(" hooks: %p\n", device->Hooks()); device_hooks* hooks = device->Hooks(); kprintf(" close() %p\n", hooks->close); kprintf(" free() %p\n", hooks->free); kprintf(" control() %p\n", hooks->control); kprintf(" read() %p\n", hooks->read); kprintf(" write() %p\n", hooks->write); kprintf(" select() %p\n", hooks->select); kprintf(" deselect() %p\n", hooks->deselect); dump_driver(device->Driver()); return 0; } static int dump_driver(int argc, char** argv) { if (argc < 2) { // print list of all drivers kprintf("address image used publ. pri name\n"); DriverTable::Iterator iterator(sDriverHash); while (iterator.HasNext()) { legacy_driver* driver = iterator.Next(); kprintf("%p %5" B_PRId32 " %3" B_PRIu32 " %5" B_PRId32 " %c " "%3" B_PRId32 " %s\n", driver, driver->image < 0 ? -1 : driver->image, driver->devices_used, driver->devices.Count(), driver->binary_updated ? 'U' : ' ', driver->priority, driver->name); } return 0; } if (!strcmp(argv[1], "--help")) { kprintf("usage: %s [name]\n", argv[0]); return 0; } legacy_driver* driver = sDriverHash->Lookup(argv[1]); if (driver == NULL) { kprintf("Driver named \"%s\" not found.\n", argv[1]); return 0; } dump_driver(driver); return 0; } // #pragma mark - DirectoryIterator::DirectoryIterator(const char* path, const char* subPath, bool recursive) : fDirectory(NULL), fBasePath(NULL), fCurrentName(NULL) { SetTo(path, subPath, recursive); } DirectoryIterator::~DirectoryIterator() { Unset(); } void DirectoryIterator::SetTo(const char* path, const char* subPath, bool recursive) { Unset(); fRecursive = recursive; if (path == NULL) { // add default paths KPath pathBuffer; bool disableUserAddOns = get_safemode_boolean( B_SAFEMODE_DISABLE_USER_ADD_ONS, false); for (uint32 i = 0; i < sizeof(kDriverPaths) / sizeof(kDriverPaths[0]); i++) { if (i < 3 && disableUserAddOns) continue; if (__find_directory(kDriverPaths[i], gBootDevice, true, pathBuffer.LockBuffer(), pathBuffer.BufferSize()) == B_OK) { pathBuffer.UnlockBuffer(); pathBuffer.Append("kernel"); AddPath(pathBuffer.Path(), subPath); } else pathBuffer.UnlockBuffer(); } } else AddPath(path, subPath); } status_t DirectoryIterator::GetNext(KPath& path, struct stat& stat) { next_directory: while (fDirectory == NULL) { delete fBasePath; fBasePath = NULL; if (!fPaths.Pop(&fBasePath)) return B_ENTRY_NOT_FOUND; fDirectory = opendir(fBasePath->Path()); } next_entry: struct dirent* dirent = readdir(fDirectory); if (dirent == NULL) { // get over to next directory on the stack closedir(fDirectory); fDirectory = NULL; goto next_directory; } if (!strcmp(dirent->d_name, "..") || !strcmp(dirent->d_name, ".")) goto next_entry; fCurrentName = dirent->d_name; path.SetTo(fBasePath->Path()); path.Append(fCurrentName); if (::stat(path.Path(), &stat) != 0) goto next_entry; if (S_ISDIR(stat.st_mode) && fRecursive) { KPath* nextPath = new(nothrow) KPath(path); if (!nextPath) return B_NO_MEMORY; if (fPaths.Push(nextPath) != B_OK) return B_NO_MEMORY; goto next_entry; } return B_OK; } void DirectoryIterator::Unset() { if (fDirectory != NULL) { closedir(fDirectory); fDirectory = NULL; } delete fBasePath; fBasePath = NULL; KPath* path; while (fPaths.Pop(&path)) delete path; } void DirectoryIterator::AddPath(const char* basePath, const char* subPath) { KPath* path = new(nothrow) KPath(basePath); if (!path) panic("out of memory"); if (subPath != NULL) path->Append(subPath); fPaths.Push(path); } // #pragma mark - DirectoryWatcher::DirectoryWatcher() { } DirectoryWatcher::~DirectoryWatcher() { } void DirectoryWatcher::EventOccurred(NotificationService& service, const KMessage* event) { int32 opcode = event->GetInt32("opcode", -1); dev_t device = event->GetInt32("device", -1); ino_t directory = event->GetInt64("directory", -1); const char* name = event->GetString("name", NULL); if (opcode == B_ENTRY_MOVED) { // Determine whether it's a move within, out of, or into one // of our watched directories. ino_t from = event->GetInt64("from directory", -1); ino_t to = event->GetInt64("to directory", -1); if (sDirectoryNodeHash.Lookup(&from) == NULL) { directory = to; opcode = B_ENTRY_CREATED; } else if (sDirectoryNodeHash.Lookup(&to) == NULL) { directory = from; opcode = B_ENTRY_REMOVED; } else { // Move within, don't do anything for now // TODO: adjust driver priority if necessary return; } } KPath path(B_PATH_NAME_LENGTH + 1); if (path.InitCheck() != B_OK || vfs_entry_ref_to_path(device, directory, name, true, path.LockBuffer(), path.BufferSize()) != B_OK) return; path.UnlockBuffer(); dprintf("driver \"%s\" %s\n", path.Leaf(), opcode == B_ENTRY_CREATED ? "added" : "removed"); driver_event* driverEvent = new(std::nothrow) driver_event( opcode == B_ENTRY_CREATED ? kAddDriver : kRemoveDriver); if (driverEvent == NULL) return; strlcpy(driverEvent->path, path.Path(), sizeof(driverEvent->path)); MutexLocker _(sDriverEventsLock); sDriverEvents.Add(driverEvent); atomic_add(&sDriverEventsPending, 1); } // #pragma mark - static void start_watching(const char* base, const char* sub) { KPath path(base); path.Append(sub); // TODO: create missing directories? struct stat stat; if (::stat(path.Path(), &stat) != 0) return; add_node_listener(stat.st_dev, stat.st_ino, B_WATCH_DIRECTORY, sDirectoryWatcher); directory_node_entry* entry = new(std::nothrow) directory_node_entry; if (entry != NULL) { entry->node = stat.st_ino; sDirectoryNodeHash.Insert(entry); } } static struct driver_entry* new_driver_entry(const char* path, dev_t device, ino_t node) { driver_entry* entry = new(std::nothrow) driver_entry; if (entry == NULL) return NULL; entry->path = strdup(path); if (entry->path == NULL) { delete entry; return NULL; } entry->device = device; entry->node = node; entry->busses = 0; return entry; } /*! Iterates over the given list and tries to load all drivers in that list. The list is emptied and freed during the traversal. */ static status_t try_drivers(DriverEntryList& list) { while (true) { driver_entry* entry = list.RemoveHead(); if (entry == NULL) break; image_id image = load_kernel_add_on(entry->path); if (image >= 0) { // check if it's an old-style driver if (legacy_driver_add(entry->path) == B_OK) { // we have a driver dprintf("loaded driver %s\n", entry->path); } unload_kernel_add_on(image); } free(entry->path); delete entry; } return B_OK; } static status_t probe_for_drivers(const char* type) { TRACE(("probe_for_drivers(type = %s)\n", type)); if (gBootDevice < 0) return B_OK; DriverEntryList drivers; // build list of potential drivers for that type DirectoryIterator iterator(NULL, type, false); struct stat stat; KPath path; while (iterator.GetNext(path, stat) == B_OK) { if (S_ISDIR(stat.st_mode)) { add_node_listener(stat.st_dev, stat.st_ino, B_WATCH_DIRECTORY, sDirectoryWatcher); directory_node_entry* entry = new(std::nothrow) directory_node_entry; if (entry != NULL) { entry->node = stat.st_ino; sDirectoryNodeHash.Insert(entry); } // We need to make sure that drivers in ie. "audio/raw/" can // be found as well - therefore, we must make sure that "audio" // exists on /dev. size_t length = strlen("drivers/dev"); if (strncmp(type, "drivers/dev", length)) continue; path.SetTo(type); path.Append(iterator.CurrentName()); devfs_publish_directory(path.Path() + length + 1); continue; } driver_entry* entry = new_driver_entry(path.Path(), stat.st_dev, stat.st_ino); if (entry == NULL) return B_NO_MEMORY; TRACE(("found potential driver: %s\n", path.Path())); drivers.Add(entry); } if (drivers.IsEmpty()) return B_OK; // ToDo: do something with the remaining drivers... :) try_drivers(drivers); return B_OK; } // #pragma mark - LegacyDevice LegacyDevice::LegacyDevice(legacy_driver* driver, const char* path, device_hooks* hooks) : fDriver(driver), fRepublished(true), fRemovedFromParent(false) { fDeviceModule = (device_module_info*)malloc(sizeof(device_module_info)); if (fDeviceModule != NULL) memset(fDeviceModule, 0, sizeof(device_module_info)); fDeviceData = this; fPath = strdup(path); SetHooks(hooks); } LegacyDevice::~LegacyDevice() { free(fDeviceModule); free((char*)fPath); } status_t LegacyDevice::InitCheck() const { return fDeviceModule != NULL && fPath != NULL ? B_OK : B_NO_MEMORY; } status_t LegacyDevice::InitDevice() { RecursiveLocker _(sLock); if (fInitialized++ > 0) return B_OK; if (fDriver != NULL && fDriver->devices_used == 0 && (fDriver->image < 0 || fDriver->binary_updated)) { status_t status = reload_driver(fDriver); if (status < B_OK) return status; } if (fDriver != NULL) fDriver->devices_used++; return B_OK; } void LegacyDevice::UninitDevice() { RecursiveLocker _(sLock); if (fInitialized-- > 1) return; if (fDriver != NULL) { if (--fDriver->devices_used == 0 && fDriver->devices.IsEmpty()) unload_driver(fDriver); fDriver = NULL; } } void LegacyDevice::Removed() { RecursiveLocker _(sLock); if (!fRemovedFromParent && fDriver != NULL) fDriver->devices.Remove(this); delete this; } status_t LegacyDevice::Control(void* _cookie, int32 op, void* buffer, size_t length) { switch (op) { case B_GET_DRIVER_FOR_DEVICE: if (length != 0 && length <= strlen(fDriver->path)) return ERANGE; return user_strlcpy(static_cast(buffer), fDriver->path, length); default: return AbstractModuleDevice::Control(_cookie, op, buffer, length); } } void LegacyDevice::SetHooks(device_hooks* hooks) { // TODO: setup compatibility layer! fHooks = hooks; fDeviceModule->close = hooks->close; fDeviceModule->free = hooks->free; fDeviceModule->control = hooks->control; fDeviceModule->read = hooks->read; fDeviceModule->write = hooks->write; if (fDriver == NULL || fDriver->api_version >= 2) { // According to Be newsletter, vol II, issue 36, // version 2 added readv/writev, which we don't support, but also // select/deselect. if (hooks->select != NULL) { // Note we set the module's select to a non-null value to indicate // that we have select. HasSelect() will therefore return the // correct answer. As Select() is virtual our compatibility // version below is going to be called though, that redirects to // the proper select hook, so it is ok to set it to an invalid // address here. fDeviceModule->select = (status_t (*)(void*, uint8, selectsync*))~0; } fDeviceModule->deselect = hooks->deselect; } } status_t LegacyDevice::Open(const char* path, int openMode, void** _cookie) { return Hooks()->open(path, openMode, _cookie); } status_t LegacyDevice::Select(void* cookie, uint8 event, selectsync* sync) { return Hooks()->select(cookie, event, 0, sync); } // #pragma mark - kernel private API extern "C" void legacy_driver_add_preloaded(kernel_args* args) { // NOTE: This function does not exit in case of error, since it // needs to unload the images then. Also the return code of // the path operations is kept separate from the add_driver() // success, so that even if add_driver() fails for one driver, it // is still tried for the other drivers. // NOTE: The initialization success of the path objects is implicitely // checked by the immediately following functions. KPath basePath; status_t status = __find_directory(B_SYSTEM_ADDONS_DIRECTORY, gBootDevice, false, basePath.LockBuffer(), basePath.BufferSize()); if (status != B_OK) { dprintf("legacy_driver_add_preloaded: find_directory() failed: " "%s\n", strerror(status)); } basePath.UnlockBuffer(); if (status == B_OK) status = basePath.Append("kernel"); if (status != B_OK) { dprintf("legacy_driver_add_preloaded: constructing base driver " "path failed: %s\n", strerror(status)); return; } struct preloaded_image* image; for (image = args->preloaded_images; image != NULL; image = image->next) { if (image->is_module || image->id < 0) continue; KPath imagePath(basePath); status = imagePath.Append(image->name); // try to add the driver TRACE(("legacy_driver_add_preloaded: adding driver %s\n", imagePath.Path())); if (status == B_OK) status = add_driver(imagePath.Path(), image->id); if (status != B_OK) { dprintf("legacy_driver_add_preloaded: Failed to add \"%s\": %s\n", (char*)image->name, strerror(status)); unload_kernel_add_on(image->id); } } } extern "C" status_t legacy_driver_add(const char* path) { return add_driver(path, -1); } extern "C" status_t legacy_driver_publish(const char* path, device_hooks* hooks) { // we don't have a driver, just publish the hooks LegacyDevice* device = new(std::nothrow) LegacyDevice(NULL, path, hooks); if (device == NULL) return B_NO_MEMORY; status_t status = device->InitCheck(); if (status == B_OK) status = devfs_publish_device(path, device); if (status != B_OK) delete device; return status; } extern "C" status_t legacy_driver_rescan(const char* driverName) { RecursiveLocker locker(sLock); legacy_driver* driver = sDriverHash->Lookup(driverName); if (driver == NULL) return B_ENTRY_NOT_FOUND; // Republish the driver's entries return republish_driver(driver); } extern "C" status_t legacy_driver_probe(const char* subPath) { TRACE(("legacy_driver_probe(type = %s)\n", subPath)); char devicePath[64]; snprintf(devicePath, sizeof(devicePath), "drivers/dev%s%s", subPath[0] ? "/" : "", subPath); if (!sWatching && gBootDevice > 0) { // We're probing the actual boot volume for the first time, // let's watch its driver directories for changes KPath path; new(&sDirectoryWatcher) DirectoryWatcher; bool disableUserAddOns = get_safemode_boolean( B_SAFEMODE_DISABLE_USER_ADD_ONS, false); for (uint32 i = 0; i < sizeof(kDriverPaths) / sizeof(kDriverPaths[0]); i++) { if (i < 3 && disableUserAddOns) continue; if (__find_directory(kDriverPaths[i], gBootDevice, true, path.LockBuffer(), path.BufferSize()) == B_OK) { path.UnlockBuffer(); path.Append("kernel/drivers"); start_watching(path.Path(), "bin"); } else path.UnlockBuffer(); } sWatching = true; } return probe_for_drivers(devicePath); } extern "C" status_t legacy_driver_init(void) { sDriverHash = new(std::nothrow) DriverTable(); if (sDriverHash == NULL || sDriverHash->Init(DRIVER_HASH_SIZE) != B_OK) return B_NO_MEMORY; recursive_lock_init(&sLock, "legacy driver"); new(&sDriverWatcher) DriverWatcher; new(&sDriverEvents) DriverEventList; register_kernel_daemon(&handle_driver_events, NULL, 10); // once every second add_debugger_command("legacy_driver", &dump_driver, "info about a legacy driver entry"); add_debugger_command("legacy_device", &dump_device, "info about a legacy device"); return B_OK; }