/* * Copyright 2002-2008, Axel Dörfler, axeld@pinc-software.de. All rights reserved. * Distributed under the terms of the MIT License. * * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved. * Distributed under the terms of the NewOS License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "IOScheduler.h" //#define TRACE_DEVFS #ifdef TRACE_DEVFS # define TRACE(x) dprintf x #else # define TRACE(x) #endif struct devfs_partition { struct devfs_vnode *raw_device; partition_info info; }; struct driver_entry; enum { kNotScanned = 0, kBootScan, kNormalScan, }; struct devfs_stream { mode_t type; union { struct stream_dir { struct devfs_vnode *dir_head; struct list cookies; int32 scanned; } dir; struct stream_dev { device_node_info *node; pnp_devfs_driver_info *info; device_hooks *ops; struct devfs_partition *partition; IOScheduler *scheduler; driver_entry *driver; } dev; struct stream_symlink { const char *path; size_t length; } symlink; } u; }; struct devfs_vnode { struct devfs_vnode *all_next; ino_t id; char *name; time_t modification_time; time_t creation_time; uid_t uid; gid_t gid; struct devfs_vnode *parent; struct devfs_vnode *dir_next; struct devfs_stream stream; }; #define DEVFS_HASH_SIZE 16 struct devfs { dev_t id; recursive_lock lock; int32 next_vnode_id; hash_table *vnode_hash; struct devfs_vnode *root_vnode; hash_table *driver_hash; }; struct devfs_dir_cookie { struct list_link link; struct devfs_vnode *current; int32 state; // iteration state }; struct devfs_cookie { void *device_cookie; }; // directory iteration states enum { ITERATION_STATE_DOT = 0, ITERATION_STATE_DOT_DOT = 1, ITERATION_STATE_OTHERS = 2, ITERATION_STATE_BEGIN = ITERATION_STATE_DOT, }; struct driver_entry { driver_entry *next; const char *path; const char *name; dev_t device; ino_t node; time_t last_modified; image_id image; uint32 devices_published; uint32 devices_used; bool binary_updated; int32 priority; // 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); }; struct path_entry : DoublyLinkedListLinkImpl { char path[B_PATH_NAME_LENGTH]; }; class DriverWatcher : public NotificationListener { public: DriverWatcher(); virtual ~DriverWatcher(); virtual void EventOccured(NotificationService& service, const KMessage* event); }; static status_t get_node_for_path(struct devfs *fs, const char *path, struct devfs_vnode **_node); static void get_device_name(struct devfs_vnode *vnode, char *buffer, size_t size); static status_t unpublish_node(struct devfs *fs, devfs_vnode *node, mode_t type); static status_t publish_device(struct devfs *fs, const char *path, device_node_info *deviceNode, pnp_devfs_driver_info *info, driver_entry *driver, device_hooks *ops, int32 apiVersion); static status_t unload_driver(driver_entry *driver); static status_t load_driver(driver_entry *driver); /* the one and only allowed devfs instance */ static struct devfs *sDeviceFileSystem = NULL; static int32 sDefaultApiVersion = 1; static DriverWatcher sDriverWatcher; static int32 sDriverEvents; static DoublyLinkedList sDriversToAdd; // #pragma mark - driver private static uint32 driver_entry_hash(void *_driver, const void *_key, uint32 range) { driver_entry *driver = (driver_entry *)_driver; const char *key = (const char *)_key; if (driver != NULL) return hash_hash_string(driver->name) % range; return hash_hash_string(key) % range; } static int driver_entry_compare(void *_driver, const void *_key) { driver_entry *driver = (driver_entry *)_driver; const char *key = (const char *)_key; return strcmp(driver->name, key); } /*! 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(driver_entry *driver) { if (driver->image < 0) { // The driver is not yet loaded - go through the normal load procedure return load_driver(driver); } RecursiveLocker locker(&sDeviceFileSystem->lock); // build the list of currently present devices of this driver // by iterating through all present nodes struct hash_iterator i; hash_open(sDeviceFileSystem->vnode_hash, &i); DoublyLinkedList currentNodes; while (true) { devfs_vnode *vnode = (devfs_vnode *)hash_next( sDeviceFileSystem->vnode_hash, &i); if (vnode == NULL) break; if (S_ISCHR(vnode->stream.type) && vnode->stream.u.dev.driver == driver) { path_entry *entry = new(std::nothrow) path_entry; if (entry == NULL) { while ((entry = currentNodes.RemoveHead())) delete entry; hash_close(sDeviceFileSystem->vnode_hash, &i, false); return B_NO_MEMORY; } get_device_name(vnode, entry->path, sizeof(entry->path)); currentNodes.Add(entry); } } hash_close(sDeviceFileSystem->vnode_hash, &i, 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++) { bool present = false; path_entry *entry = currentNodes.Head(); while (entry) { if (strncmp(entry->path, devicePaths[0], B_PATH_NAME_LENGTH) == 0) { // this device was present before and still is -> no republish currentNodes.Remove(entry); delete entry; exported++; present = true; break; } entry = currentNodes.GetNext(entry); } device_hooks *hooks = driver->find_device(devicePaths[0]); if (hooks == NULL) continue; if (present) { // update hooks devfs_vnode *vnode; status_t status = get_node_for_path(sDeviceFileSystem, devicePaths[0], &vnode); if (status != B_OK) return status; vnode->stream.u.dev.ops = hooks; continue; } // the device was not present before -> publish it now TRACE(("devfs: publishing new device \"%s\"\n", devicePaths[0])); if (publish_device(sDeviceFileSystem, devicePaths[0], NULL, NULL, driver, hooks, driver->api_version) == B_OK) exported++; } // what's left in currentNodes was present but is not anymore -> unpublish path_entry *entry = currentNodes.Head(); while (entry) { TRACE(("devfs: unpublishing no more present \"%s\"\n", entry->path)); devfs_unpublish_device(entry->path, true); path_entry *next = currentNodes.GetNext(entry); currentNodes.Remove(entry); delete entry; entry = next; } if (exported == 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(driver_entry *driver) { status_t (*init_hardware)(void); status_t (*init_driver)(void); const char **devicePaths; int32 exported = 0; 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 %ld 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); error3: if (driver->uninit_driver) driver->uninit_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(driver_entry *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; } /*! Collects all devices belonging to the \a driver and unpublishs them. */ static void unpublish_driver(driver_entry *driver) { RecursiveLocker locker(&sDeviceFileSystem->lock); // Iterate through all nodes until all devices of this driver have // been unpublished while (driver->devices_published > 0) { struct hash_iterator i; hash_open(sDeviceFileSystem->vnode_hash, &i); while (true) { devfs_vnode *vnode = (devfs_vnode *)hash_next( sDeviceFileSystem->vnode_hash, &i); if (vnode == NULL) break; if (S_ISCHR(vnode->stream.type) && vnode->stream.u.dev.driver == driver) { void *dummy; get_vnode(sDeviceFileSystem->id, vnode->id, &dummy); // We need to get/put the node, so that it is // actually removed unpublish_node(sDeviceFileSystem, vnode, S_IFCHR); put_vnode(sDeviceFileSystem->id, vnode->id); break; } } hash_close(sDeviceFileSystem->vnode_hash, &i, false); } } static int32 get_priority(const char *path) { // TODO: use find_directory() const char *kPaths[] = {"/boot/beos", "/boot/common", "/boot/home", NULL}; for (int32 i = 0; kPaths[i] != NULL; i++) { if (!strncmp(kPaths[i], path, strlen(kPaths[i]))) return i; } return -1; } static const char * get_leaf(const char *path) { const char *name = strrchr(path, '/'); if (name == NULL) return path; return name + 1; } static driver_entry * find_driver(dev_t device, ino_t node) { hash_iterator iterator; hash_open(sDeviceFileSystem->driver_hash, &iterator); driver_entry *driver; while (true) { driver = (driver_entry *)hash_next(sDeviceFileSystem->driver_hash, &iterator); if (driver == NULL || driver->device == device && driver->node == node) break; } hash_close(sDeviceFileSystem->driver_hash, &iterator, false); return driver; } static status_t add_driver(const char *path, image_id image) { // see 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 locker(&sDeviceFileSystem->lock); driver_entry *driver = (driver_entry *)hash_lookup( sDeviceFileSystem->driver_hash, get_leaf(path)); if (driver != NULL) { // we know this driver // 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 = (driver_entry *)malloc(sizeof(driver_entry)); 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_mtime; driver->devices_published = 0; 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; hash_insert(sDeviceFileSystem->driver_hash, driver); if (stat.st_dev > 0) add_node_listener(stat.st_dev, stat.st_ino, B_WATCH_STAT, sDriverWatcher); // 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 */ 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 int32 scan_mode(void) { // We may scan every device twice: // - once before there is a boot device, // - and once when there is one return gBootDevice >= 0 ? kNormalScan : kBootScan; } static status_t scan_for_drivers(devfs_vnode *dir) { KPath path; if (path.InitCheck() != B_OK) return B_NO_MEMORY; get_device_name(dir, path.LockBuffer(), path.BufferSize()); path.UnlockBuffer(); TRACE(("scan_for_drivers: mode %ld: %s\n", scan_mode(), path.Path())); // scan for drivers at this path probe_for_device_type(path.Path()); dir->stream.u.dir.scanned = scan_mode(); return B_OK; } static status_t reload_driver(driver_entry *driver) { dprintf("devfs: reload driver \"%s\"\n", driver->name); unload_driver(driver); status_t status = load_driver(driver); if (status < B_OK) unpublish_driver(driver); return status; } static void handle_driver_events(void *_fs, int /*iteration*/) { struct devfs *fs = (devfs *)_fs; if (atomic_and(&sDriverEvents, 0) == 0) return; // something happened, let's see what it was RecursiveLocker locker(fs->lock); while (true) { path_entry *path = sDriversToAdd.RemoveHead(); if (path == NULL) break; devfs_add_driver(path->path); delete path; } hash_iterator iterator; hash_open(sDeviceFileSystem->driver_hash, &iterator); driver_entry *driver; while (true) { driver = (driver_entry *)hash_next(sDeviceFileSystem->driver_hash, &iterator); if (driver == NULL) break; if (!driver->binary_updated || driver->devices_used != 0) continue; // try to reload the driver reload_driver(driver); } hash_close(sDeviceFileSystem->driver_hash, &iterator, false); } // #pragma mark - DriverWatcher DriverWatcher::DriverWatcher() { } DriverWatcher::~DriverWatcher() { } void DriverWatcher::EventOccured(NotificationService& service, const KMessage* event) { if (event->GetInt32("opcode", -1) != B_STAT_CHANGED || (event->GetInt32("fields", 0) & B_STAT_MODIFICATION_TIME) == 0) return; RecursiveLocker locker(&sDeviceFileSystem->lock); driver_entry *driver = find_driver(event->GetInt32("device", -1), event->GetInt64("node", 0)); if (driver == NULL) return; driver->binary_updated = true; //dprintf("%s: devices published %ld, used %ld\n", driver->name, driver->devices_published, driver->devices_used); if (driver->devices_used == 0) { // trigger a reload of the driver atomic_add(&sDriverEvents, 1); } else { // driver is in use right now dprintf("devfs: changed driver \"%s\" is still in use\n", driver->name); } } // #pragma mark - devfs private static uint32 devfs_vnode_hash(void *_vnode, const void *_key, uint32 range) { struct devfs_vnode *vnode = (struct devfs_vnode *)_vnode; const ino_t *key = (const ino_t *)_key; if (vnode != NULL) return vnode->id % range; return (uint64)*key % range; } static int devfs_vnode_compare(void *_vnode, const void *_key) { struct devfs_vnode *vnode = (struct devfs_vnode *)_vnode; const ino_t *key = (const ino_t *)_key; if (vnode->id == *key) return 0; return -1; } static struct devfs_vnode * devfs_create_vnode(struct devfs *fs, devfs_vnode *parent, const char *name) { struct devfs_vnode *vnode; vnode = (struct devfs_vnode *)malloc(sizeof(struct devfs_vnode)); if (vnode == NULL) return NULL; memset(vnode, 0, sizeof(struct devfs_vnode)); vnode->id = fs->next_vnode_id++; vnode->name = strdup(name); if (vnode->name == NULL) { free(vnode); return NULL; } vnode->creation_time = vnode->modification_time = time(NULL); vnode->uid = geteuid(); vnode->gid = parent ? parent->gid : getegid(); // inherit group from parent if possible return vnode; } static status_t devfs_delete_vnode(struct devfs *fs, struct devfs_vnode *vnode, bool force_delete) { // cant delete it if it's in a directory or is a directory // and has children if (!force_delete && ((S_ISDIR(vnode->stream.type) && vnode->stream.u.dir.dir_head != NULL) || vnode->dir_next != NULL)) return B_NOT_ALLOWED; // remove it from the global hash table hash_remove(fs->vnode_hash, vnode); if (S_ISCHR(vnode->stream.type)) { // for partitions, we have to release the raw device if (vnode->stream.u.dev.partition) put_vnode(fs->id, vnode->stream.u.dev.partition->raw_device->id); else delete vnode->stream.u.dev.scheduler; // remove API conversion from old to new drivers if (vnode->stream.u.dev.node == NULL) free(vnode->stream.u.dev.info); } free(vnode->name); free(vnode); return B_OK; } /*! Makes sure none of the dircookies point to the vnode passed in */ static void update_dir_cookies(struct devfs_vnode *dir, struct devfs_vnode *vnode) { struct devfs_dir_cookie *cookie = NULL; while ((cookie = (devfs_dir_cookie *)list_get_next_item( &dir->stream.u.dir.cookies, cookie)) != NULL) { if (cookie->current == vnode) cookie->current = vnode->dir_next; } } static struct devfs_vnode * devfs_find_in_dir(struct devfs_vnode *dir, const char *path) { struct devfs_vnode *vnode; if (!S_ISDIR(dir->stream.type)) return NULL; if (!strcmp(path, ".")) return dir; if (!strcmp(path, "..")) return dir->parent; for (vnode = dir->stream.u.dir.dir_head; vnode; vnode = vnode->dir_next) { //TRACE(("devfs_find_in_dir: looking at entry '%s'\n", vnode->name)); if (strcmp(vnode->name, path) == 0) { //TRACE(("devfs_find_in_dir: found it at %p\n", vnode)); return vnode; } } return NULL; } static status_t devfs_insert_in_dir(struct devfs_vnode *dir, struct devfs_vnode *vnode) { if (!S_ISDIR(dir->stream.type)) return B_BAD_VALUE; // make sure the directory stays sorted alphabetically devfs_vnode *node = dir->stream.u.dir.dir_head, *last = NULL; while (node && strcmp(node->name, vnode->name) < 0) { last = node; node = node->dir_next; } if (last == NULL) { // the new vnode is the first entry in the list vnode->dir_next = dir->stream.u.dir.dir_head; dir->stream.u.dir.dir_head = vnode; } else { // insert after that node vnode->dir_next = last->dir_next; last->dir_next = vnode; } vnode->parent = dir; dir->modification_time = time(NULL); notify_entry_created(sDeviceFileSystem->id, dir->id, vnode->name, vnode->id); notify_stat_changed(sDeviceFileSystem->id, dir->id, B_STAT_MODIFICATION_TIME); return B_OK; } static status_t devfs_remove_from_dir(struct devfs_vnode *dir, struct devfs_vnode *removeNode) { struct devfs_vnode *vnode = dir->stream.u.dir.dir_head; struct devfs_vnode *lastNode = NULL; for (; vnode != NULL; lastNode = vnode, vnode = vnode->dir_next) { if (vnode == removeNode) { // make sure no dircookies point to this vnode update_dir_cookies(dir, vnode); if (lastNode) lastNode->dir_next = vnode->dir_next; else dir->stream.u.dir.dir_head = vnode->dir_next; vnode->dir_next = NULL; dir->modification_time = time(NULL); notify_entry_removed(sDeviceFileSystem->id, dir->id, vnode->name, vnode->id); notify_stat_changed(sDeviceFileSystem->id, dir->id, B_STAT_MODIFICATION_TIME); return B_OK; } } return B_ENTRY_NOT_FOUND; } static status_t add_partition(struct devfs *fs, struct devfs_vnode *device, const char *name, const partition_info &info) { struct devfs_vnode *partitionNode; status_t status; if (!S_ISCHR(device->stream.type)) return B_BAD_VALUE; // we don't support nested partitions if (device->stream.u.dev.partition) return B_BAD_VALUE; // reduce checks to a minimum - things like negative offsets could be useful if (info.size < 0) return B_BAD_VALUE; // create partition struct devfs_partition *partition = (struct devfs_partition *)malloc( sizeof(struct devfs_partition)); if (partition == NULL) return B_NO_MEMORY; memcpy(&partition->info, &info, sizeof(partition_info)); RecursiveLocker locker(&fs->lock); // you cannot change a partition once set if (devfs_find_in_dir(device->parent, name)) { status = B_BAD_VALUE; goto err1; } // increase reference count of raw device - // the partition device really needs it status = get_vnode(fs->id, device->id, (fs_vnode *)&partition->raw_device); if (status < B_OK) goto err1; // now create the partition vnode partitionNode = devfs_create_vnode(fs, device->parent, name); if (partitionNode == NULL) { status = B_NO_MEMORY; goto err2; } partitionNode->stream.type = device->stream.type; partitionNode->stream.u.dev.node = device->stream.u.dev.node; partitionNode->stream.u.dev.info = device->stream.u.dev.info; partitionNode->stream.u.dev.ops = device->stream.u.dev.ops; partitionNode->stream.u.dev.partition = partition; partitionNode->stream.u.dev.scheduler = device->stream.u.dev.scheduler; hash_insert(fs->vnode_hash, partitionNode); devfs_insert_in_dir(device->parent, partitionNode); TRACE(("add_partition(name = %s, offset = %Ld, size = %Ld)\n", name, info.offset, info.size)); return B_OK; err2: put_vnode(fs->id, device->id); err1: free(partition); return status; } static inline void translate_partition_access(devfs_partition *partition, off_t &offset, size_t &size) { ASSERT(offset >= 0); ASSERT(offset < partition->info.size); size = min_c(size, partition->info.size - offset); offset += partition->info.offset; } static pnp_devfs_driver_info * create_new_driver_info(device_hooks *ops, int32 version) { pnp_devfs_driver_info *info = (pnp_devfs_driver_info *)malloc( sizeof(pnp_devfs_driver_info)); if (info == NULL) return NULL; memset(info, 0, sizeof(driver_module_info)); info->open = NULL; // ops->open is used directly for old devices info->close = ops->close; info->free = ops->free; info->control = ops->control; info->read = ops->read; info->write = ops->write; // depends on api_version info->select = NULL; info->deselect = NULL; info->read_pages = NULL; info->write_pages = NULL; // old devices can't know how to do physical page access if (version >= 2) { // According to Be newsletter, vol II, issue 36, // version 2 added readv/writev, which we don't support, but also // select/deselect. info->select = ops->select; info->deselect = ops->deselect; // ops->readv; // ops->writev; // we don't implement scatter-gather atm, so ignore those. } return info; } static status_t get_node_for_path(struct devfs *fs, const char *path, struct devfs_vnode **_node) { return vfs_get_fs_node_from_path(fs->id, path, true, (void **)_node); } static status_t unpublish_node(struct devfs *fs, devfs_vnode *node, mode_t type) { if ((node->stream.type & S_IFMT) != type) return B_BAD_TYPE; recursive_lock_lock(&fs->lock); status_t status = devfs_remove_from_dir(node->parent, node); if (status < B_OK) goto out; status = remove_vnode(fs->id, node->id); if (status == B_OK && S_ISCHR(node->stream.type) && node->stream.u.dev.driver != NULL) { node->stream.u.dev.driver->devices_published--; } out: recursive_lock_unlock(&fs->lock); return status; } static status_t unpublish_node(struct devfs *fs, const char *path, mode_t type) { devfs_vnode *node; status_t status = get_node_for_path(fs, path, &node); if (status != B_OK) return status; status = unpublish_node(fs, node, type); put_vnode(fs->id, node->id); return status; } static status_t publish_directory(struct devfs *fs, const char *path) { ASSERT_LOCKED_RECURSIVE(&fs->lock); // copy the path over to a temp buffer so we can munge it KPath tempPath(path); if (tempPath.InitCheck() != B_OK) return B_NO_MEMORY; char *temp = tempPath.LockBuffer(); // create the path leading to the device // parse the path passed in, stripping out '/' struct devfs_vnode *dir = fs->root_vnode; struct devfs_vnode *vnode = NULL; status_t status = B_OK; int32 i = 0, last = 0; while (temp[last]) { if (temp[i] == '/') { temp[i] = '\0'; i++; } else if (temp[i] != '\0') { i++; continue; } //TRACE(("\tpath component '%s'\n", &temp[last])); // we have a path component vnode = devfs_find_in_dir(dir, &temp[last]); if (vnode) { if (S_ISDIR(vnode->stream.type)) { last = i; dir = vnode; continue; } // we hit something on our path that's not a directory status = B_FILE_EXISTS; goto out; } else { vnode = devfs_create_vnode(fs, dir, &temp[last]); if (!vnode) { status = B_NO_MEMORY; goto out; } } // set up the new directory vnode->stream.type = S_IFDIR | 0755; vnode->stream.u.dir.dir_head = NULL; list_init(&vnode->stream.u.dir.cookies); hash_insert(sDeviceFileSystem->vnode_hash, vnode); devfs_insert_in_dir(dir, vnode); last = i; dir = vnode; } out: return status; } static status_t publish_node(struct devfs *fs, const char *path, struct devfs_vnode **_node) { ASSERT_LOCKED_MUTEX(&fs->lock); // copy the path over to a temp buffer so we can munge it KPath tempPath(path); if (tempPath.InitCheck() != B_OK) return B_NO_MEMORY; char *temp = tempPath.LockBuffer(); // create the path leading to the device // parse the path passed in, stripping out '/' struct devfs_vnode *dir = fs->root_vnode; struct devfs_vnode *vnode = NULL; status_t status = B_OK; int32 i = 0, last = 0; bool atLeaf = false; for (;;) { if (temp[i] == '\0') { atLeaf = true; // we'll be done after this one } else if (temp[i] == '/') { temp[i] = '\0'; i++; } else { i++; continue; } //TRACE(("\tpath component '%s'\n", &temp[last])); // we have a path component vnode = devfs_find_in_dir(dir, &temp[last]); if (vnode) { if (!atLeaf) { // we are not at the leaf of the path, so as long as // this is a dir we're okay if (S_ISDIR(vnode->stream.type)) { last = i; dir = vnode; continue; } } // we are at the leaf and hit another node // or we aren't but hit a non-dir node. // we're screwed status = B_FILE_EXISTS; goto out; } else { vnode = devfs_create_vnode(fs, dir, &temp[last]); if (!vnode) { status = B_NO_MEMORY; goto out; } } // set up the new vnode if (!atLeaf) { // this is a dir vnode->stream.type = S_IFDIR | 0755; vnode->stream.u.dir.dir_head = NULL; list_init(&vnode->stream.u.dir.cookies); } else { // this is the last component *_node = vnode; } hash_insert(sDeviceFileSystem->vnode_hash, vnode); devfs_insert_in_dir(dir, vnode); if (atLeaf) break; last = i; dir = vnode; } out: return status; } static status_t publish_device(struct devfs *fs, const char *path, device_node_info *deviceNode, pnp_devfs_driver_info *info, driver_entry *driver, device_hooks *ops, int32 apiVersion) { TRACE(("publish_device(path = \"%s\", node = %p, info = %p, hooks = %p, apiVersion = %ld)\n", path, deviceNode, info, ops, apiVersion)); if (sDeviceFileSystem == NULL) { panic("publish_device() called before devfs mounted\n"); return B_ERROR; } if ((ops == NULL && (deviceNode == NULL || info == NULL)) || path == NULL || path[0] == '\0' || path[0] == '/') return B_BAD_VALUE; // are the provided device hooks okay? if ((ops != NULL && (ops->open == NULL || ops->close == NULL || ops->read == NULL || ops->write == NULL)) || info != NULL && (info->open == NULL || info->close == NULL || info->read == NULL || info->write == NULL)) return B_BAD_VALUE; // mark disk devices - they might get an I/O scheduler bool isDisk = false; if (!strncmp(path, "disk/", 5)) isDisk = true; struct devfs_vnode *node; status_t status; RecursiveLocker locker(&fs->lock); status = publish_node(fs, path, &node); if (status != B_OK) return status; // all went fine, let's initialize the node node->stream.type = S_IFCHR | 0644; if (deviceNode == NULL) { info = create_new_driver_info(ops, apiVersion); if (!info) return B_NO_MEMORY; } node->stream.u.dev.info = info; node->stream.u.dev.node = deviceNode; node->stream.u.dev.driver = driver; node->stream.u.dev.ops = ops; if (driver != NULL) driver->devices_published++; // every raw disk gets an I/O scheduler object attached // ToDo: the driver should ask for a scheduler (ie. using its devfs node attributes) if (isDisk && !strcmp(node->name, "raw")) { node->stream.u.dev.scheduler = new(nothrow) IOScheduler(path, info); if (!node->stream.u.dev.scheduler) return B_NO_MEMORY; } return B_OK; } /*! Construct complete device name (as used for device_open()). This is safe to use only when the device is in use (and therefore cannot be unpublished during the iteration). */ static void get_device_name(struct devfs_vnode *vnode, char *buffer, size_t size) { struct devfs_vnode *leaf = vnode; size_t offset = 0; // count levels for (; vnode->parent && vnode->parent != vnode; vnode = vnode->parent) { offset += strlen(vnode->name) + 1; } // construct full path name for (vnode = leaf; vnode->parent && vnode->parent != vnode; vnode = vnode->parent) { size_t length = strlen(vnode->name); size_t start = offset - length - 1; if (size >= offset) { strcpy(buffer + start, vnode->name); if (vnode != leaf) buffer[offset - 1] = '/'; } offset = start; } } static int dump_node(int argc, char **argv) { if (argc < 2 || !strcmp(argv[1], "--help")) { kprintf("usage: %s
\n", argv[0]); return 0; } struct devfs_vnode *vnode = (struct devfs_vnode *)parse_expression(argv[1]); if (vnode == NULL) { kprintf("invalid node address\n"); return 0; } kprintf("DEVFS NODE: %p\n", vnode); kprintf(" id: %Ld\n", vnode->id); kprintf(" name: \"%s\"\n", vnode->name); kprintf(" type: %x\n", vnode->stream.type); kprintf(" parent: %p\n", vnode->parent); kprintf(" dir next: %p\n", vnode->dir_next); if (S_ISDIR(vnode->stream.type)) { kprintf(" dir scanned: %ld\n", vnode->stream.u.dir.scanned); kprintf(" contents:\n"); devfs_vnode *children = vnode->stream.u.dir.dir_head; while (children != NULL) { kprintf(" %p, id %Ld\n", children, children->id); children = children->dir_next; } } else if (S_ISLNK(vnode->stream.type)) { kprintf(" symlink to: %s\n", vnode->stream.u.symlink.path); } else { kprintf(" device node: %p\n", vnode->stream.u.dev.node); kprintf(" driver info: %p\n", vnode->stream.u.dev.info); kprintf(" hooks: %p\n", vnode->stream.u.dev.ops); kprintf(" partition: %p\n", vnode->stream.u.dev.partition); kprintf(" scheduler: %p\n", vnode->stream.u.dev.scheduler); kprintf(" driver: %p\n", vnode->stream.u.dev.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"); hash_iterator iterator; hash_open(sDeviceFileSystem->driver_hash, &iterator); while (true) { driver_entry *driver = (driver_entry *)hash_next( sDeviceFileSystem->driver_hash, &iterator); if (driver == NULL) break; kprintf("%p %5ld %3ld %5ld %c %3ld %s\n", driver, driver->image < 0 ? -1 : driver->image, driver->devices_used, driver->devices_published, driver->binary_updated ? 'U' : ' ', driver->priority, driver->name); } hash_close(sDeviceFileSystem->driver_hash, &iterator, false); return 0; } if (!strcmp(argv[1], "--help")) { kprintf("usage: %s [name]\n", argv[0]); return 0; } driver_entry *driver = (driver_entry *)hash_lookup( sDeviceFileSystem->driver_hash, argv[1]); if (driver == NULL) { kprintf("Driver named \"%s\" not found.\n", argv[1]); return 0; } kprintf("DEVFS DRIVER: %p\n", driver); kprintf(" name: %s\n", driver->name); kprintf(" path: %s\n", driver->path); kprintf(" image: %ld\n", driver->image); kprintf(" device: %ld\n", driver->device); kprintf(" node: %Ld\n", driver->node); kprintf(" last modified: %ld\n", driver->last_modified); kprintf(" devs used: %ld\n", driver->devices_used); kprintf(" devs published: %ld\n", driver->devices_published); kprintf(" binary updated: %d\n", driver->binary_updated); kprintf(" priority: %ld\n", driver->priority); kprintf(" api version: %ld\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); return 0; } // #pragma mark - file system interface static status_t devfs_mount(dev_t id, const char *devfs, uint32 flags, const char *args, fs_volume *_fs, ino_t *root_vnid) { struct devfs_vnode *vnode; struct devfs *fs; status_t err; TRACE(("devfs_mount: entry\n")); if (sDeviceFileSystem) { TRACE(("double mount of devfs attempted\n")); err = B_ERROR; goto err; } fs = (struct devfs *)malloc(sizeof(struct devfs)); if (fs == NULL) { err = B_NO_MEMORY; goto err; } fs->id = id; fs->next_vnode_id = 0; err = recursive_lock_init(&fs->lock, "devfs lock"); if (err < B_OK) goto err1; fs->vnode_hash = hash_init(DEVFS_HASH_SIZE, offsetof(devfs_vnode, all_next), //(addr_t)&vnode->all_next - (addr_t)vnode, &devfs_vnode_compare, &devfs_vnode_hash); if (fs->vnode_hash == NULL) { err = B_NO_MEMORY; goto err2; } fs->driver_hash = hash_init(DEVFS_HASH_SIZE, offsetof(driver_entry, next), &driver_entry_compare, &driver_entry_hash); if (fs->driver_hash == NULL) { err = B_NO_MEMORY; goto err3; } new(&sDriverWatcher) DriverWatcher; new(&sDriversToAdd) DoublyLinkedList; register_kernel_daemon(&handle_driver_events, fs, 10); // once every second // create a vnode vnode = devfs_create_vnode(fs, NULL, ""); if (vnode == NULL) { err = B_NO_MEMORY; goto err4; } // set it up vnode->parent = vnode; // create a dir stream for it to hold vnode->stream.type = S_IFDIR | 0755; vnode->stream.u.dir.dir_head = NULL; list_init(&vnode->stream.u.dir.cookies); fs->root_vnode = vnode; hash_insert(fs->vnode_hash, vnode); publish_vnode(id, vnode->id, vnode); *root_vnid = vnode->id; *_fs = fs; sDeviceFileSystem = fs; return B_OK; err4: hash_uninit(fs->driver_hash); err3: hash_uninit(fs->vnode_hash); err2: recursive_lock_destroy(&fs->lock); err1: free(fs); err: return err; } static status_t devfs_unmount(fs_volume _fs) { struct devfs *fs = (struct devfs *)_fs; struct devfs_vnode *vnode; struct hash_iterator i; TRACE(("devfs_unmount: entry fs = %p\n", fs)); unregister_kernel_daemon(&handle_driver_events, fs); recursive_lock_lock(&fs->lock); while (true) { path_entry *entry = sDriversToAdd.RemoveHead(); if (entry == NULL) break; delete entry; } // release the reference to the root put_vnode(fs->id, fs->root_vnode->id); // delete all of the vnodes hash_open(fs->vnode_hash, &i); while ((vnode = (devfs_vnode *)hash_next(fs->vnode_hash, &i)) != NULL) { devfs_delete_vnode(fs, vnode, true); } hash_close(fs->vnode_hash, &i, false); hash_uninit(fs->vnode_hash); hash_uninit(fs->driver_hash); recursive_lock_destroy(&fs->lock); free(fs); return B_OK; } static status_t devfs_sync(fs_volume fs) { TRACE(("devfs_sync: entry\n")); return B_OK; } static status_t devfs_lookup(fs_volume _fs, fs_vnode _dir, const char *name, ino_t *_id, int *_type) { struct devfs *fs = (struct devfs *)_fs; struct devfs_vnode *dir = (struct devfs_vnode *)_dir; struct devfs_vnode *vnode, *vdummy; status_t status; TRACE(("devfs_lookup: entry dir %p, name '%s'\n", dir, name)); if (!S_ISDIR(dir->stream.type)) return B_NOT_A_DIRECTORY; RecursiveLocker locker(&fs->lock); if (dir->stream.u.dir.scanned < scan_mode()) scan_for_drivers(dir); // look it up vnode = devfs_find_in_dir(dir, name); if (vnode == NULL) { // We don't have to rescan here, because thanks to node monitoring // we already know it does not exist return B_ENTRY_NOT_FOUND; } status = get_vnode(fs->id, vnode->id, (fs_vnode *)&vdummy); if (status < B_OK) return status; *_id = vnode->id; *_type = vnode->stream.type; return B_OK; } static status_t devfs_get_vnode_name(fs_volume _fs, fs_vnode _vnode, char *buffer, size_t bufferSize) { struct devfs_vnode *vnode = (struct devfs_vnode *)_vnode; TRACE(("devfs_get_vnode_name: vnode = %p\n", vnode)); strlcpy(buffer, vnode->name, bufferSize); return B_OK; } static status_t devfs_get_vnode(fs_volume _fs, ino_t id, fs_vnode *_vnode, bool reenter) { struct devfs *fs = (struct devfs *)_fs; TRACE(("devfs_get_vnode: asking for vnode id = %Ld, vnode = %p, r %d\n", id, _vnode, reenter)); RecursiveLocker _(fs->lock); struct devfs_vnode *vnode = (devfs_vnode *)hash_lookup(fs->vnode_hash, &id); if (vnode == NULL) return B_ENTRY_NOT_FOUND; TRACE(("devfs_get_vnode: looked it up at %p\n", *_vnode)); *_vnode = vnode; return B_OK; } static status_t devfs_put_vnode(fs_volume _fs, fs_vnode _v, bool reenter) { #ifdef TRACE_DEVFS struct devfs_vnode *vnode = (struct devfs_vnode *)_v; TRACE(("devfs_put_vnode: entry on vnode %p, id = %Ld, reenter %d\n", vnode, vnode->id, reenter)); #endif return B_OK; } static status_t devfs_remove_vnode(fs_volume _fs, fs_vnode _v, bool reenter) { struct devfs *fs = (struct devfs *)_fs; struct devfs_vnode *vnode = (struct devfs_vnode *)_v; TRACE(("devfs_removevnode: remove %p (%Ld), reenter %d\n", vnode, vnode->id, reenter)); RecursiveLocker locker(&fs->lock); if (vnode->dir_next) { // can't remove node if it's linked to the dir panic("devfs_removevnode: vnode %p asked to be removed is present in dir\n", vnode); } devfs_delete_vnode(fs, vnode, false); return B_OK; } static status_t devfs_create(fs_volume _fs, fs_vnode _dir, const char *name, int openMode, int perms, fs_cookie *_cookie, ino_t *_newVnodeID) { struct devfs_vnode *dir = (struct devfs_vnode *)_dir; struct devfs *fs = (struct devfs *)_fs; struct devfs_cookie *cookie; struct devfs_vnode *vnode, *vdummy; status_t status = B_OK; TRACE(("devfs_create: dir %p, name \"%s\", openMode 0x%x, fs_cookie %p \n", dir, name, openMode, _cookie)); RecursiveLocker locker(&fs->lock); // look it up vnode = devfs_find_in_dir(dir, name); if (!vnode) { status = EROFS; goto err1; } if (openMode & O_EXCL) return B_FILE_EXISTS; status = get_vnode(fs->id, vnode->id, (fs_vnode *)&vdummy); if (status < B_OK) goto err1; *_newVnodeID = vnode->id; cookie = (struct devfs_cookie *)malloc(sizeof(struct devfs_cookie)); if (cookie == NULL) { status = B_NO_MEMORY; goto err2; } if (S_ISCHR(vnode->stream.type)) { if (vnode->stream.u.dev.node != NULL) { status = vnode->stream.u.dev.info->open( vnode->stream.u.dev.node->parent->cookie, openMode, &cookie->device_cookie); } else { char buffer[B_FILE_NAME_LENGTH]; get_device_name(vnode, buffer, sizeof(buffer)); status = vnode->stream.u.dev.ops->open(buffer, openMode, &cookie->device_cookie); } } if (status < B_OK) goto err3; *_cookie = cookie; return B_OK; err3: free(cookie); err2: put_vnode(fs->id, vnode->id); err1: return status; } static status_t devfs_open(fs_volume _fs, fs_vnode _vnode, int openMode, fs_cookie *_cookie) { struct devfs_vnode *vnode = (struct devfs_vnode *)_vnode; struct devfs *fs = (struct devfs *)_fs; struct devfs_cookie *cookie; status_t status = B_OK; TRACE(("devfs_open: vnode %p, openMode 0x%x, fs_cookie %p \n", vnode, openMode, _cookie)); cookie = (struct devfs_cookie *)malloc(sizeof(struct devfs_cookie)); if (cookie == NULL) return B_NO_MEMORY; if (S_ISCHR(vnode->stream.type)) { RecursiveLocker locker(fs->lock); driver_entry *driver = vnode->stream.u.dev.driver; // TODO: we might want to check if the current node does still exist // (it should fail in the driver's open(), though, if it doesn't) if (driver != NULL && (driver->image < 0 || driver->binary_updated)) { status = reload_driver(driver); if (status < B_OK) return status; } locker.Unlock(); if (vnode->stream.u.dev.node != NULL) { status = vnode->stream.u.dev.info->open( vnode->stream.u.dev.node->parent->cookie, openMode, &cookie->device_cookie); } else { char buffer[B_FILE_NAME_LENGTH]; get_device_name(vnode, buffer, sizeof(buffer)); status = vnode->stream.u.dev.ops->open(buffer, openMode, &cookie->device_cookie); } locker.Lock(); if (status == B_OK && driver != NULL) driver->devices_used++; } if (status < B_OK) free(cookie); else *_cookie = cookie; return status; } static status_t devfs_close(fs_volume _fs, fs_vnode _vnode, fs_cookie _cookie) { struct devfs_vnode *vnode = (struct devfs_vnode *)_vnode; struct devfs_cookie *cookie = (struct devfs_cookie *)_cookie; TRACE(("devfs_close: entry vnode %p, cookie %p\n", vnode, cookie)); if (S_ISCHR(vnode->stream.type)) { // pass the call through to the underlying device return vnode->stream.u.dev.info->close(cookie->device_cookie); } return B_OK; } static status_t devfs_free_cookie(fs_volume _fs, fs_vnode _vnode, fs_cookie _cookie) { struct devfs_vnode *vnode = (struct devfs_vnode *)_vnode; struct devfs_cookie *cookie = (struct devfs_cookie *)_cookie; struct devfs *fs = (struct devfs *)_fs; TRACE(("devfs_freecookie: entry vnode %p, cookie %p\n", vnode, cookie)); if (S_ISCHR(vnode->stream.type)) { // pass the call through to the underlying device vnode->stream.u.dev.info->free(cookie->device_cookie); RecursiveLocker _(fs->lock); if (vnode->stream.u.dev.driver != NULL) vnode->stream.u.dev.driver->devices_used--; } free(cookie); return B_OK; } static status_t devfs_fsync(fs_volume _fs, fs_vnode _v) { return B_OK; } static status_t devfs_read_link(fs_volume _fs, fs_vnode _link, char *buffer, size_t *_bufferSize) { struct devfs_vnode *link = (struct devfs_vnode *)_link; size_t bufferSize = *_bufferSize; if (!S_ISLNK(link->stream.type)) return B_BAD_VALUE; if (link->stream.u.symlink.length < *_bufferSize) *_bufferSize = link->stream.u.symlink.length; memcpy(buffer, link->stream.u.symlink.path, *_bufferSize); return B_OK; } static status_t devfs_read(fs_volume _fs, fs_vnode _vnode, fs_cookie _cookie, off_t pos, void *buffer, size_t *_length) { struct devfs_vnode *vnode = (struct devfs_vnode *)_vnode; struct devfs_cookie *cookie = (struct devfs_cookie *)_cookie; //TRACE(("devfs_read: vnode %p, cookie %p, pos %Ld, len %p\n", // vnode, cookie, pos, _length)); if (!S_ISCHR(vnode->stream.type)) return B_BAD_VALUE; if (pos < 0) return B_BAD_VALUE; if (vnode->stream.u.dev.partition) { if (pos >= vnode->stream.u.dev.partition->info.size) return B_BAD_VALUE; translate_partition_access(vnode->stream.u.dev.partition, pos, *_length); } if (*_length == 0) return B_OK; // if this device has an I/O scheduler attached, the request must go through it if (IOScheduler *scheduler = vnode->stream.u.dev.scheduler) { IORequest request(cookie->device_cookie, pos, buffer, *_length); status_t status = scheduler->Process(request); if (status == B_OK) *_length = request.Size(); return status; } // pass the call through to the device return vnode->stream.u.dev.info->read(cookie->device_cookie, pos, buffer, _length); } static status_t devfs_write(fs_volume _fs, fs_vnode _vnode, fs_cookie _cookie, off_t pos, const void *buffer, size_t *_length) { struct devfs_vnode *vnode = (struct devfs_vnode *)_vnode; struct devfs_cookie *cookie = (struct devfs_cookie *)_cookie; //TRACE(("devfs_write: vnode %p, cookie %p, pos %Ld, len %p\n", // vnode, cookie, pos, _length)); if (!S_ISCHR(vnode->stream.type)) return B_BAD_VALUE; if (pos < 0) return B_BAD_VALUE; if (vnode->stream.u.dev.partition) { if (pos >= vnode->stream.u.dev.partition->info.size) return B_BAD_VALUE; translate_partition_access(vnode->stream.u.dev.partition, pos, *_length); } if (*_length == 0) return B_OK; if (IOScheduler *scheduler = vnode->stream.u.dev.scheduler) { IORequest request(cookie->device_cookie, pos, buffer, *_length); status_t status = scheduler->Process(request); if (status == B_OK) *_length = request.Size(); return status; } return vnode->stream.u.dev.info->write(cookie->device_cookie, pos, buffer, _length); } static status_t devfs_create_dir(fs_volume _fs, fs_vnode _dir, const char *name, int perms, ino_t *_newVnodeID) { struct devfs *fs = (struct devfs *)_fs; struct devfs_vnode *dir = (struct devfs_vnode *)_dir; struct devfs_vnode *vnode = devfs_find_in_dir(dir, name); if (vnode != NULL) { return EEXIST; } vnode = devfs_create_vnode(fs, dir, name); if (vnode == NULL) { return B_NO_MEMORY; } // set up the new directory vnode->stream.type = S_IFDIR | perms; vnode->stream.u.dir.dir_head = NULL; list_init(&vnode->stream.u.dir.cookies); hash_insert(sDeviceFileSystem->vnode_hash, vnode); devfs_insert_in_dir(dir, vnode); *_newVnodeID = vnode->id; return B_OK; } static status_t devfs_open_dir(fs_volume _fs, fs_vnode _vnode, fs_cookie *_cookie) { struct devfs *fs = (struct devfs *)_fs; struct devfs_vnode *vnode = (struct devfs_vnode *)_vnode; struct devfs_dir_cookie *cookie; TRACE(("devfs_open_dir: vnode %p\n", vnode)); if (!S_ISDIR(vnode->stream.type)) return B_BAD_VALUE; cookie = (devfs_dir_cookie *)malloc(sizeof(devfs_dir_cookie)); if (cookie == NULL) return B_NO_MEMORY; RecursiveLocker locker(&fs->lock); // make sure the directory has up-to-date contents if (vnode->stream.u.dir.scanned < scan_mode()) scan_for_drivers(vnode); cookie->current = vnode->stream.u.dir.dir_head; cookie->state = ITERATION_STATE_BEGIN; list_add_item(&vnode->stream.u.dir.cookies, cookie); *_cookie = cookie; return B_OK; } static status_t devfs_free_dir_cookie(fs_volume _fs, fs_vnode _vnode, fs_cookie _cookie) { struct devfs_vnode *vnode = (struct devfs_vnode *)_vnode; struct devfs_dir_cookie *cookie = (devfs_dir_cookie *)_cookie; struct devfs *fs = (struct devfs *)_fs; TRACE(("devfs_free_dir_cookie: entry vnode %p, cookie %p\n", vnode, cookie)); RecursiveLocker locker(&fs->lock); list_remove_item(&vnode->stream.u.dir.cookies, cookie); free(cookie); return B_OK; } static status_t devfs_read_dir(fs_volume _fs, fs_vnode _vnode, fs_cookie _cookie, struct dirent *dirent, size_t bufferSize, uint32 *_num) { struct devfs_vnode *vnode = (devfs_vnode *)_vnode; struct devfs_dir_cookie *cookie = (devfs_dir_cookie *)_cookie; struct devfs *fs = (struct devfs *)_fs; status_t status = B_OK; struct devfs_vnode *childNode = NULL; const char *name = NULL; struct devfs_vnode *nextChildNode = NULL; int32 nextState = cookie->state; TRACE(("devfs_read_dir: vnode %p, cookie %p, buffer %p, size %ld\n", _vnode, cookie, dirent, bufferSize)); if (!S_ISDIR(vnode->stream.type)) return B_BAD_VALUE; RecursiveLocker locker(&fs->lock); switch (cookie->state) { case ITERATION_STATE_DOT: childNode = vnode; name = "."; nextChildNode = vnode->stream.u.dir.dir_head; nextState = cookie->state + 1; break; case ITERATION_STATE_DOT_DOT: childNode = vnode->parent; name = ".."; nextChildNode = vnode->stream.u.dir.dir_head; nextState = cookie->state + 1; break; default: childNode = cookie->current; if (childNode) { name = childNode->name; nextChildNode = childNode->dir_next; } break; } if (!childNode) { *_num = 0; return B_OK; } dirent->d_dev = fs->id; dirent->d_ino = childNode->id; dirent->d_reclen = strlen(name) + sizeof(struct dirent); if (dirent->d_reclen > bufferSize) return ENOBUFS; status = user_strlcpy(dirent->d_name, name, bufferSize - sizeof(struct dirent)); if (status < B_OK) return status; cookie->current = nextChildNode; cookie->state = nextState; return B_OK; } static status_t devfs_rewind_dir(fs_volume _fs, fs_vnode _vnode, fs_cookie _cookie) { struct devfs_vnode *vnode = (struct devfs_vnode *)_vnode; struct devfs_dir_cookie *cookie = (devfs_dir_cookie *)_cookie; struct devfs *fs = (struct devfs *)_fs; TRACE(("devfs_rewind_dir: vnode %p, cookie %p\n", _vnode, _cookie)); if (!S_ISDIR(vnode->stream.type)) return B_BAD_VALUE; RecursiveLocker locker(&fs->lock); cookie->current = vnode->stream.u.dir.dir_head; cookie->state = ITERATION_STATE_BEGIN; return B_OK; } /*! Forwards the opcode to the device driver, but also handles some devfs specific functionality, like partitions. */ static status_t devfs_ioctl(fs_volume _fs, fs_vnode _vnode, fs_cookie _cookie, ulong op, void *buffer, size_t length) { struct devfs *fs = (struct devfs *)_fs; struct devfs_vnode *vnode = (struct devfs_vnode *)_vnode; struct devfs_cookie *cookie = (struct devfs_cookie *)_cookie; TRACE(("devfs_ioctl: vnode %p, cookie %p, op %ld, buf %p, len %ld\n", _vnode, _cookie, op, buffer, length)); // we are actually checking for a *device* here, we don't make the // distinction between char and block devices if (S_ISCHR(vnode->stream.type)) { switch (op) { case B_GET_GEOMETRY: { struct devfs_partition *partition = vnode->stream.u.dev.partition; if (partition == NULL) break; device_geometry geometry; status_t status = vnode->stream.u.dev.info->control( cookie->device_cookie, op, &geometry, length); if (status < B_OK) return status; // patch values to match partition size geometry.sectors_per_track = 0; if (geometry.bytes_per_sector == 0) geometry.bytes_per_sector = 512; geometry.sectors_per_track = partition->info.size / geometry.bytes_per_sector; geometry.head_count = 1; geometry.cylinder_count = 1; return user_memcpy(buffer, &geometry, sizeof(device_geometry)); } case B_GET_DRIVER_FOR_DEVICE: { const char *path; if (!vnode->stream.u.dev.driver) return B_ENTRY_NOT_FOUND; path = vnode->stream.u.dev.driver->path; if (path == NULL) return B_ENTRY_NOT_FOUND; return user_strlcpy((char *)buffer, path, B_FILE_NAME_LENGTH); } case B_GET_PARTITION_INFO: { struct devfs_partition *partition = vnode->stream.u.dev.partition; if (!S_ISCHR(vnode->stream.type) || partition == NULL || length != sizeof(partition_info)) return B_BAD_VALUE; return user_memcpy(buffer, &partition->info, sizeof(partition_info)); } case B_SET_PARTITION: return B_NOT_ALLOWED; case B_GET_PATH_FOR_DEVICE: { char path[256]; status_t err; /* TODO: we might want to actually find the mountpoint * of that instance of devfs... * but for now we assume it's mounted on /dev */ strcpy(path, "/dev/"); get_device_name(vnode, path + 5, sizeof(path) - 5); if (length && (length <= strlen(path))) return ERANGE; return user_strlcpy((char *)buffer, path, sizeof(path)); } // old unsupported R5 private stuff case B_GET_NEXT_OPEN_DEVICE: dprintf("devfs: unsupported legacy ioctl B_GET_NEXT_OPEN_DEVICE\n"); return B_NOT_SUPPORTED; case B_ADD_FIXED_DRIVER: dprintf("devfs: unsupported legacy ioctl B_ADD_FIXED_DRIVER\n"); return B_NOT_SUPPORTED; case B_REMOVE_FIXED_DRIVER: dprintf("devfs: unsupported legacy ioctl B_REMOVE_FIXED_DRIVER\n"); return B_NOT_SUPPORTED; } return vnode->stream.u.dev.info->control(cookie->device_cookie, op, buffer, length); } return B_BAD_VALUE; } static status_t devfs_set_flags(fs_volume _fs, fs_vnode _vnode, fs_cookie _cookie, int flags) { struct devfs_vnode *vnode = (struct devfs_vnode *)_vnode; struct devfs_cookie *cookie = (struct devfs_cookie *)_cookie; // we need to pass the O_NONBLOCK flag to the underlying device if (!S_ISCHR(vnode->stream.type)) return B_NOT_ALLOWED; return vnode->stream.u.dev.info->control(cookie->device_cookie, flags & O_NONBLOCK ? B_SET_NONBLOCKING_IO : B_SET_BLOCKING_IO, NULL, 0); } static status_t devfs_select(fs_volume _fs, fs_vnode _vnode, fs_cookie _cookie, uint8 event, uint32 ref, selectsync *sync) { struct devfs_vnode *vnode = (struct devfs_vnode *)_vnode; struct devfs_cookie *cookie = (struct devfs_cookie *)_cookie; if (!S_ISCHR(vnode->stream.type)) return B_NOT_ALLOWED; // If the device has no select() hook, notify select() now. if (!vnode->stream.u.dev.info->select) return notify_select_event((selectsync*)sync, event); return vnode->stream.u.dev.info->select(cookie->device_cookie, event, ref, (selectsync*)sync); } static status_t devfs_deselect(fs_volume _fs, fs_vnode _vnode, fs_cookie _cookie, uint8 event, selectsync *sync) { struct devfs_vnode *vnode = (struct devfs_vnode *)_vnode; struct devfs_cookie *cookie = (struct devfs_cookie *)_cookie; if (!S_ISCHR(vnode->stream.type)) return B_NOT_ALLOWED; // If the device has no select() hook, notify select() now. if (!vnode->stream.u.dev.info->deselect) return B_OK; return vnode->stream.u.dev.info->deselect(cookie->device_cookie, event, (selectsync*)sync); } static bool devfs_can_page(fs_volume _fs, fs_vnode _vnode, fs_cookie cookie) { struct devfs_vnode *vnode = (devfs_vnode *)_vnode; //TRACE(("devfs_canpage: vnode %p\n", vnode)); if (!S_ISCHR(vnode->stream.type) || vnode->stream.u.dev.node == NULL || cookie == NULL) return false; return vnode->stream.u.dev.info->read_pages != NULL || vnode->stream.u.dev.info->read != NULL; } static status_t devfs_read_pages(fs_volume _fs, fs_vnode _vnode, fs_cookie _cookie, off_t pos, const iovec *vecs, size_t count, size_t *_numBytes, bool reenter) { struct devfs_vnode *vnode = (devfs_vnode *)_vnode; struct devfs_cookie *cookie = (struct devfs_cookie *)_cookie; //TRACE(("devfs_read_pages: vnode %p, vecs %p, count = %lu, pos = %Ld, size = %lu\n", vnode, vecs, count, pos, *_numBytes)); if (!S_ISCHR(vnode->stream.type) || (vnode->stream.u.dev.info->read_pages == NULL && vnode->stream.u.dev.info->read == NULL) || cookie == NULL) return B_NOT_ALLOWED; if (pos < 0) return B_BAD_VALUE; if (vnode->stream.u.dev.partition) { if (pos >= vnode->stream.u.dev.partition->info.size) return B_BAD_VALUE; translate_partition_access(vnode->stream.u.dev.partition, pos, *_numBytes); } if (vnode->stream.u.dev.info->read_pages) { return vnode->stream.u.dev.info->read_pages(cookie->device_cookie, pos, vecs, count, _numBytes); } // emulate read_pages() using read() status_t error = B_OK; size_t bytesTransferred = 0; size_t remainingBytes = *_numBytes; for (size_t i = 0; i < count && remainingBytes > 0; i++) { size_t toRead = min_c(vecs[i].iov_len, remainingBytes); size_t length = toRead; error = vnode->stream.u.dev.info->read(cookie->device_cookie, pos, vecs[i].iov_base, &length); if (error != B_OK) break; pos += length; bytesTransferred += length; remainingBytes -= length; if (length < toRead) break; } *_numBytes = bytesTransferred; return (bytesTransferred > 0 ? B_OK : error); } static status_t devfs_write_pages(fs_volume _fs, fs_vnode _vnode, fs_cookie _cookie, off_t pos, const iovec *vecs, size_t count, size_t *_numBytes, bool reenter) { struct devfs_vnode *vnode = (devfs_vnode *)_vnode; struct devfs_cookie *cookie = (struct devfs_cookie *)_cookie; //TRACE(("devfs_write_pages: vnode %p, vecs %p, count = %lu, pos = %Ld, size = %lu\n", vnode, vecs, count, pos, *_numBytes)); if (!S_ISCHR(vnode->stream.type) || (vnode->stream.u.dev.info->write_pages == NULL && vnode->stream.u.dev.info->write == NULL) || cookie == NULL) return B_NOT_ALLOWED; if (pos < 0) return B_BAD_VALUE; if (vnode->stream.u.dev.partition) { if (pos >= vnode->stream.u.dev.partition->info.size) return B_BAD_VALUE; translate_partition_access(vnode->stream.u.dev.partition, pos, *_numBytes); } if (vnode->stream.u.dev.info->write_pages) { return vnode->stream.u.dev.info->write_pages(cookie->device_cookie, pos, vecs, count, _numBytes); } // emulate write_pages() using write() status_t error = B_OK; size_t bytesTransferred = 0; size_t remainingBytes = *_numBytes; for (size_t i = 0; i < count && remainingBytes > 0; i++) { size_t toWrite = min_c(vecs[i].iov_len, remainingBytes); size_t length = toWrite; error = vnode->stream.u.dev.info->write(cookie->device_cookie, pos, vecs[i].iov_base, &length); if (error != B_OK) break; pos += length; bytesTransferred += length; remainingBytes -= length; if (length < toWrite) break; } *_numBytes = bytesTransferred; return (bytesTransferred > 0 ? B_OK : error); } static status_t devfs_read_stat(fs_volume _fs, fs_vnode _vnode, struct stat *stat) { struct devfs_vnode *vnode = (struct devfs_vnode *)_vnode; TRACE(("devfs_read_stat: vnode %p (%Ld), stat %p\n", vnode, vnode->id, stat)); stat->st_ino = vnode->id; stat->st_size = 0; stat->st_mode = vnode->stream.type; stat->st_nlink = 1; stat->st_blksize = 65536; stat->st_uid = vnode->uid; stat->st_gid = vnode->gid; stat->st_atime = time(NULL); stat->st_mtime = stat->st_ctime = vnode->modification_time; stat->st_crtime = vnode->creation_time; // ToDo: this only works for partitions right now - if we should decide // to keep this feature, we should have a better solution if (S_ISCHR(vnode->stream.type)) { //device_geometry geometry; // if it's a real block device, then let's report a useful size if (vnode->stream.u.dev.partition != NULL) { stat->st_size = vnode->stream.u.dev.partition->info.size; #if 0 } else if (vnode->stream.u.dev.info->control(cookie->device_cookie, B_GET_GEOMETRY, &geometry, sizeof(struct device_geometry)) >= B_OK) { stat->st_size = 1LL * geometry.head_count * geometry.cylinder_count * geometry.sectors_per_track * geometry.bytes_per_sector; #endif } // is this a real block device? then let's have it reported like that if (stat->st_size != 0) stat->st_mode = S_IFBLK | (vnode->stream.type & S_IUMSK); } else if (S_ISLNK(vnode->stream.type)) { stat->st_size = vnode->stream.u.symlink.length; } return B_OK; } static status_t devfs_write_stat(fs_volume _fs, fs_vnode _vnode, const struct stat *stat, uint32 statMask) { struct devfs *fs = (struct devfs *)_fs; struct devfs_vnode *vnode = (struct devfs_vnode *)_vnode; TRACE(("devfs_write_stat: vnode %p (0x%Lx), stat %p\n", vnode, vnode->id, stat)); // we cannot change the size of anything if (statMask & B_STAT_SIZE) return B_BAD_VALUE; RecursiveLocker locker(&fs->lock); if (statMask & B_STAT_MODE) { vnode->stream.type = (vnode->stream.type & ~S_IUMSK) | (stat->st_mode & S_IUMSK); } if (statMask & B_STAT_UID) vnode->uid = stat->st_uid; if (statMask & B_STAT_GID) vnode->gid = stat->st_gid; if (statMask & B_STAT_MODIFICATION_TIME) vnode->modification_time = stat->st_mtime; if (statMask & B_STAT_CREATION_TIME) vnode->creation_time = stat->st_crtime; notify_stat_changed(fs->id, vnode->id, statMask); return B_OK; } static status_t devfs_std_ops(int32 op, ...) { switch (op) { case B_MODULE_INIT: add_debugger_command("devfs_node", &dump_node, "info about a private devfs node"); add_debugger_command("devfs_driver", &dump_driver, "info about a devfs driver entry"); return B_OK; case B_MODULE_UNINIT: remove_debugger_command("devfs_node", &dump_node); remove_debugger_command("devfs_driver", &dump_driver); return B_OK; default: return B_ERROR; } } file_system_module_info gDeviceFileSystem = { { "file_systems/devfs" B_CURRENT_FS_API_VERSION, 0, devfs_std_ops, }, "Device File System", 0, // DDM flags NULL, // identify_partition() NULL, // scan_partition() NULL, // free_identify_partition_cookie() NULL, // free_partition_content_cookie() &devfs_mount, &devfs_unmount, NULL, NULL, &devfs_sync, &devfs_lookup, &devfs_get_vnode_name, &devfs_get_vnode, &devfs_put_vnode, &devfs_remove_vnode, &devfs_can_page, &devfs_read_pages, &devfs_write_pages, NULL, // get_file_map /* common */ &devfs_ioctl, &devfs_set_flags, &devfs_select, &devfs_deselect, &devfs_fsync, &devfs_read_link, NULL, // symlink NULL, // link NULL, // unlink NULL, // rename NULL, // access &devfs_read_stat, &devfs_write_stat, /* file */ &devfs_create, &devfs_open, &devfs_close, &devfs_free_cookie, &devfs_read, &devfs_write, /* directory */ &devfs_create_dir, NULL, // remove_dir &devfs_open_dir, &devfs_close, // same as for files - it does nothing for directories, anyway &devfs_free_dir_cookie, &devfs_read_dir, &devfs_rewind_dir, // the other operations are not supported (attributes, indices, queries) NULL, }; // #pragma mark - device node // temporary hack to get it to work with the current device manager static device_manager_info *sDeviceManager; static const device_attr pnp_devfs_attrs[] = { { B_DRIVER_MODULE, B_STRING_TYPE, { string: PNP_DEVFS_MODULE_NAME }}, { NULL } }; /*! Someone registered a device */ static status_t pnp_devfs_register_device(device_node_handle parent) { char *filename = NULL; device_node_handle node; status_t status; TRACE(("pnp_devfs_probe()\n")); if (sDeviceManager->get_attr_string(parent, PNP_DEVFS_FILENAME, &filename, true) != B_OK) { dprintf("devfs: Item containing file name is missing\n"); status = B_ERROR; goto err1; } TRACE(("Adding %s\n", filename)); status = sDeviceManager->register_device(parent, pnp_devfs_attrs, NULL, &node); if (status != B_OK || node == NULL) goto err1; // ToDo: this is a hack to get things working (init_driver() only // works for registered nodes) parent->registered = true; pnp_devfs_driver_info *info; status = sDeviceManager->init_driver(parent, NULL, (driver_module_info **)&info, NULL); if (status != B_OK) goto err2; //add_device(device); status = publish_device(sDeviceFileSystem, filename, node, info, NULL, NULL, 0); if (status != B_OK) goto err3; //nudge(); return B_OK; err3: sDeviceManager->uninit_driver(parent); err2: sDeviceManager->unregister_device(node); err1: free(filename); return status; } #if 0 // remove device from public list and add it to unpublish list // (devices_lock must be hold) static void pnp_devfs_remove_device(device_info *device) { TRACE(("removing device %s from public list\n", device->name)); --num_devices; REMOVE_DL_LIST( device, devices, ); ++num_unpublished_devices; ADD_DL_LIST_HEAD( device, devices_to_unpublish, ); // (don't free it even if no handle is open - the device // info block contains the hook list which may just got passed // to the devfs layer; we better wait until next // publish_devices, so we are sure that devfs won't access // the hook list anymore) } #endif // device got removed static void pnp_devfs_device_removed(device_node_handle node, void *cookie) { #if 0 device_info *device; device_node_handle parent; status_t res = B_OK; #endif TRACE(("pnp_devfs_device_removed()\n")); #if 0 parent = sDeviceManager->get_parent(node); // don't use cookie - we don't use sDeviceManager loading scheme but // global data and keep care of everything ourself! ACQUIRE_BEN( &device_list_lock ); for( device = devices; device; device = device->next ) { if( device->parent == parent ) break; } if( device != NULL ) { pnp_devfs_remove_device(device); } else { SHOW_ERROR( 0, "bug: node %p couldn't been found", node ); res = B_NAME_NOT_FOUND; } RELEASE_BEN( &device_list_lock ); //nudge(); #endif } static status_t pnp_devfs_std_ops(int32 op, ...) { switch (op) { case B_MODULE_INIT: return get_module(B_DEVICE_MANAGER_MODULE_NAME, (module_info **)&sDeviceManager); case B_MODULE_UNINIT: put_module(B_DEVICE_MANAGER_MODULE_NAME); return B_OK; default: return B_ERROR; } } driver_module_info gDeviceForDriversModule = { { PNP_DEVFS_MODULE_NAME, 0 /*B_KEEP_LOADED*/, pnp_devfs_std_ops }, NULL, // supports device pnp_devfs_register_device, NULL, // init driver NULL, // uninit driver pnp_devfs_device_removed, NULL, // cleanup NULL, // get paths }; // #pragma mark - kernel private API extern "C" void devfs_add_preloaded_drivers(kernel_args* args) { struct preloaded_image* image; for (image = args->preloaded_images; image != NULL; image = image->next) { if (!image->is_module && image->id >= 0) { // fake an absolute path char path[B_PATH_NAME_LENGTH]; strlcpy(path, "/boot/beos/system/add-ons/kernel/", sizeof(path)); strlcat(path, image->name, sizeof(path)); // try to add the driver status_t error = add_driver(path, image->id); if (error != B_OK) { dprintf("devfs_add_preloaded_drivers: Failed to add \"%s\"\n", image->name); unload_kernel_add_on(image->id); } } } } extern "C" status_t devfs_add_driver(const char *path) { return add_driver(path, -1); } extern "C" void devfs_driver_added(const char *path) { int32 priority = get_priority(path); RecursiveLocker locker(&sDeviceFileSystem->lock); driver_entry *driver = (driver_entry *)hash_lookup( sDeviceFileSystem->driver_hash, get_leaf(path)); if (driver == NULL) { // Add the driver to our list path_entry *entry = new(std::nothrow) path_entry; if (entry == NULL) return; strlcpy(entry->path, path, sizeof(entry->path)); sDriversToAdd.Add(entry); } else { // Update the driver if it is affected by the new entry if (priority < driver->priority) return; driver->binary_updated = true; } atomic_add(&sDriverEvents, 1); } extern "C" void devfs_driver_removed(const char *path) { int32 priority = get_priority(path); RecursiveLocker locker(&sDeviceFileSystem->lock); driver_entry *driver = (driver_entry *)hash_lookup( sDeviceFileSystem->driver_hash, get_leaf(path)); if (driver == NULL || priority < driver->priority) return; driver->binary_updated = true; atomic_add(&sDriverEvents, 1); } extern "C" status_t devfs_unpublish_file_device(const char *path) { return unpublish_node(sDeviceFileSystem, path, S_IFLNK); } extern "C" status_t devfs_publish_file_device(const char *path, const char *filePath) { struct devfs_vnode *node; status_t status; filePath = strdup(filePath); if (filePath == NULL) return B_NO_MEMORY; RecursiveLocker locker(&sDeviceFileSystem->lock); status = publish_node(sDeviceFileSystem, path, &node); if (status != B_OK) return status; // all went fine, let's initialize the node node->stream.type = S_IFLNK | 0644; node->stream.u.symlink.path = filePath; node->stream.u.symlink.length = strlen(filePath); return B_OK; } extern "C" status_t devfs_unpublish_partition(const char *path) { return unpublish_node(sDeviceFileSystem, path, S_IFCHR); } extern "C" status_t devfs_publish_partition(const char *path, const partition_info *info) { if (path == NULL || info == NULL) return B_BAD_VALUE; TRACE(("publish partition: %s (device \"%s\", offset %Ld, size %Ld)\n", path, info->device, info->offset, info->size)); // the partition and device paths must be the same until the leaves const char *lastPath = strrchr(path, '/'); const char *lastDevice = strrchr(info->device, '/'); if (lastPath == NULL || lastDevice == NULL) return B_BAD_VALUE; size_t length = lastDevice - (lastPath - path) - info->device; if (strncmp(path, info->device + length, lastPath - path)) return B_BAD_VALUE; devfs_vnode *device; status_t status = get_node_for_path(sDeviceFileSystem, info->device, &device); if (status != B_OK) return status; status = add_partition(sDeviceFileSystem, device, lastPath + 1, *info); put_vnode(sDeviceFileSystem->id, device->id); return status; } extern "C" status_t devfs_unpublish_device(const char *path, bool disconnect) { devfs_vnode *node; status_t status = get_node_for_path(sDeviceFileSystem, path, &node); if (status != B_OK) return status; status = unpublish_node(sDeviceFileSystem, node, S_IFCHR); if (status == B_OK && disconnect) vfs_disconnect_vnode(sDeviceFileSystem->id, node->id); put_vnode(sDeviceFileSystem->id, node->id); return status; } extern "C" status_t devfs_publish_device(const char *path, device_hooks *ops) { // post R5: assume version 2 return publish_device(sDeviceFileSystem, path, NULL, NULL, NULL, ops, 2); } extern "C" status_t devfs_publish_directory(const char *path) { RecursiveLocker locker(&sDeviceFileSystem->lock); return publish_directory(sDeviceFileSystem, path); } extern "C" status_t devfs_rescan_driver(const char *driverName) { TRACE(("devfs_rescan_driver: %s\n", driverName)); RecursiveLocker locker(&sDeviceFileSystem->lock); driver_entry *driver = (driver_entry *)hash_lookup( sDeviceFileSystem->driver_hash, driverName); if (driver == NULL) return B_ENTRY_NOT_FOUND; // Republish the driver's entries return republish_driver(driver); }