diff --git a/src/kernel/core/device_manager/Jamfile b/src/kernel/core/device_manager/Jamfile index ec94356634..81fd9165f8 100644 --- a/src/kernel/core/device_manager/Jamfile +++ b/src/kernel/core/device_manager/Jamfile @@ -7,16 +7,16 @@ KernelMergeObject kernel_device_manager.o : attributes.c # boot_hack.c device_manager.c -# driver_loader.c + driver_loader.c id_generator.c io_resources.c # module_loader.c nodes.c -# notifications.c -# patterns.c -# probe.c + notifications.c + patterns.c + probe.c registration.c -# scan.c + scan.c : -fno-pic -Wno-unused -D_KERNEL_MODE ; diff --git a/src/kernel/core/device_manager/device_manager_private.h b/src/kernel/core/device_manager/device_manager_private.h index d32d98ba18..1b481ab9c9 100644 --- a/src/kernel/core/device_manager/device_manager_private.h +++ b/src/kernel/core/device_manager/device_manager_private.h @@ -18,6 +18,7 @@ #define GENERIC_SUBDIR "/generic" // driver registration directories +#define PNP_DIR #define SYSTEM_DRIVER_REGISTRATION "/boot/beos/add-ons/kernel/"PNP_DIR"registration/" #define COMMON_DRIVER_REGISTRATION "/boot/home/config/add-ons/kernel/"PNP_DIR"registration/" diff --git a/src/kernel/core/device_manager/driver_loader.c b/src/kernel/core/device_manager/driver_loader.c new file mode 100644 index 0000000000..c295bd95f7 --- /dev/null +++ b/src/kernel/core/device_manager/driver_loader.c @@ -0,0 +1,450 @@ +/* +** Copyright 2002-04, Thomas Kurschel. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +*/ + +/* + Part of PnP Manager + + PnP Driver loader. +*/ + +#include "device_manager_private.h" + +#include + +#include +#include + + +#define TRACE_DRIVER_LOADER +#ifdef TRACE_DRIVER_LOADER +# define TRACE(x) dprintf x +#else +# define TRACE(x) ; +#endif + + +/** unblock loading of driver + * node_lock must be hold + */ + +void +pnp_unblock_load(pnp_node_info *node) +{ + if (--node->load_block_count == 0 && node->num_blocked_loads > 0) { + TRACE(("Releasing blocked loads of %p\n", node)); + + release_sem_etc(node->load_block_sem, + node->num_blocked_loads, B_DO_NOT_RESCHEDULE); + + node->num_blocked_loads = 0; + } +} + + +/** if loading of driver is blocked, wait until it is unblocked + * node_lock must be hold + */ + +static void +pnp_wait_load_block(pnp_node_info *node) +{ + if (node->load_block_count > 0) { + TRACE(("Loading of %p is blocked - wait until unblock\n", node)); + + // make sure we get informed about unblock + ++node->num_blocked_loads; + + benaphore_unlock(&gNodeLock); + + acquire_sem(node->load_block_sem); + + benaphore_lock(&gNodeLock); + + TRACE(("Continuing loading of %p\n", node)); + } +} + + +/** load driver automatically after registration/rescan of node. */ + +void +pnp_load_driver_automatically(pnp_node_info *node, bool after_rescan) +{ + uint8 always_loaded; + status_t res; + pnp_driver_info *interface; + void *cookie; + + benaphore_lock(&gNodeLock); + + // ignore dead nodes + if (!node->registered) + goto err; + + if (node->automatically_loaded) + goto err; + + if (pnp_get_attr_uint8_nolock(node, PNP_DRIVER_ALWAYS_LOADED, + &always_loaded, false ) != B_OK) + goto err; + + if (always_loaded < 1 || always_loaded > 2) + goto err; + + // this test is probably useless as the driver would be kept loaded + // during rescan anyway + if (always_loaded == 2 && after_rescan) + goto err; + + // alright - time to get the driver loaded + benaphore_unlock(&gNodeLock); + + res = pnp_load_driver(node, NULL, &interface, &cookie); + + benaphore_lock(&gNodeLock); + + // if driver cannot be loaded, we don't really care + if (res == B_OK ) + node->automatically_loaded = true; + else + dprintf("driver could not be automatically loaded\n"); + +err: + benaphore_unlock(&gNodeLock); + return; +} + + +/** unload driver that got loaded automatically. */ + +void +pnp_unload_driver_automatically(pnp_node_info *node, bool before_rescan) +{ + uint8 always_loaded; + status_t res; + + benaphore_lock(&gNodeLock); + + if (!node->automatically_loaded) + goto err; + + if (pnp_get_attr_uint8_nolock(node, PNP_DRIVER_ALWAYS_LOADED, + &always_loaded, false) != B_OK) + goto err; + + // this test is probably useless as the driver wouldn't be + // loaded anyway + if (always_loaded < 1 || always_loaded > 2) + goto err; + + if (always_loaded == 2 && before_rescan) + goto err; + + // time to unload the driver + benaphore_unlock(&gNodeLock); + + res = pnp_unload_driver(node); + + benaphore_lock(&gNodeLock); + + // don't reset flag if unloading failed; + // if this happens during unregistration, this won't help; + // but if this happens during rescan, the driver only stays + // loaded during rescan, which can be handled + if (res == B_OK) + node->automatically_loaded = false; + else + dprintf("driver could not be unloaded during scan\n"); + +err: + benaphore_unlock(&gNodeLock); + return; +} + + +/** load driver for real + * (loader_lock must be hold) + */ + +static status_t +load_driver_int(pnp_node_info *node, void *user_cookie) +{ + char *module_name; + pnp_driver_info *driver; + void *cookie; + status_t res; + + TRACE(("load_driver_int()\n")); + + res = pnp_get_attr_string(node, PNP_DRIVER_DRIVER, &module_name, false); + if (res != B_OK) + return res; + + TRACE(("%s\n", module_name)); + + res = get_module(module_name, (module_info **)&driver); + if (res < B_OK) { + dprintf("Cannot load module %s\n", module_name); + goto err; + } + + if (driver->init_device == NULL) { + dprintf("Driver %s has no init_device hook\n", module_name); + res = B_ERROR; + goto err2; + } + + res = driver->init_device(node, user_cookie, &cookie); + if (res < B_OK) + goto err2; + + node->driver = driver; + node->cookie = cookie; + + free(module_name); + return B_OK; + +err2: + put_module(module_name); + +err: + free(module_name); + return res; +} + + +/** public: load driver */ + +status_t +pnp_load_driver(pnp_node_handle node, void *user_cookie, + pnp_driver_info **interface, void **cookie) +{ + status_t res; + + TRACE(("pnp_load_driver()\n")); + + if (node == NULL) + return B_BAD_VALUE; + + benaphore_lock(&gNodeLock); + + if (!node->registered) { + benaphore_unlock(&gNodeLock); + + TRACE(("Refused loading as driver got unregistered\n")); + return B_NAME_NOT_FOUND; + } + + // increase ref_count whenever someone loads the driver + // to keep node information alive + ++node->ref_count; + + if (node->load_count == 0) { + // driver is not loaded + // tell others that someone will load the driver + // (after this, noone is allowed to block loading) + ++node->loading; + + // wait until loading is not blocked + pnp_wait_load_block(node); + + // make sure noone else load/unloads/notifies the driver + pnp_start_hook_call_nolock(node); + + // during load, driver may register children; + // probing them for consumers may be impossible as they + // may try to load driver -> deadlock + // so we block probing for consumers of children + pnp_defer_probing_of_children_nolock(node); + + if (!node->registered) { + // device got lost meanwhile + TRACE(("Device got lost during wait\n")); + res = B_NAME_NOT_FOUND; + } else if (node->load_count == 0) { + // noone loaded the driver meanwhile, so + // we have to do that + benaphore_unlock(&gNodeLock); + + TRACE(("Driver will be loaded\n")); + + res = load_driver_int(node, user_cookie); + + benaphore_lock(&gNodeLock); + } else { + // some other thread loaded the driver meanwhile + TRACE(("Driver got loaded during wait\n")); + res = B_OK; + } + + // info: the next statements may have a strange order, + // but as we own node_lock, order is not important + + // finish loading officially + // (don't release node_lock before load_count gets increased!) + --node->loading; + + // unblock concurrent load/unload/notification + pnp_finish_hook_call_nolock(node); + + if (res == B_OK) { + // everything went fine - increase load counter + ++node->load_count; + + // now we can safely probe for consumers of children as the + // driver is fully loaded + pnp_probe_waiting_children_nolock(node); + } else { + // loading failed or device got removed - restore reference count; + // but first (i.e. as long as ref_count is increased) get + // rid of waiting children + pnp_probe_waiting_children_nolock(node); + pnp_remove_node_ref_nolock(node); + } + } else { + // driver is already loaded, so increase load_count + ++node->load_count; + res = B_OK; + } + + benaphore_unlock(&gNodeLock); + + if (res == B_OK) { + if (interface) + *interface = node->driver; + + if (cookie) + *cookie = node->cookie; + + TRACE(("load_driver: Success\n")); + } else { + TRACE(("load_driver: Failure (%s)\n", strerror(res))); + } + + return res; +} + + +/** unload driver for real */ + +static status_t +unload_driver_int(pnp_node_info *node) +{ + char *module_name; + status_t res; + pnp_driver_info *driver = node->driver; + + res = pnp_get_attr_string(node, PNP_DRIVER_DRIVER, &module_name, false); + if (res != B_OK) + return res; + + TRACE(("unload_driver_int: %s\n", module_name)); + + if (driver->uninit_device == NULL) { + // it has no uninit - we can't unload it, so it stays in memory forever + TRACE(("Driver %s has no uninit_device hook\n", module_name)); + res = B_ERROR; + goto err; + } + + res = driver->uninit_device(node->cookie); + if (res != B_OK) { + TRACE(("Failed to uninit driver %s (%s)\n", module_name, strerror(res))); + goto err; + } + + // if it was unregistered a while ago but couldn't get cleaned up + // because it was in use, do the cleanup now + if (!node->registered) { + if (driver->device_cleanup != NULL) + driver->device_cleanup(node); + } + + put_module(module_name); + + free(module_name); + return B_OK; + +err: + free(module_name); + return res; +} + + +/** public: unload driver */ + +status_t +pnp_unload_driver(pnp_node_handle node) +{ + status_t res; + + TRACE(("pnp_unload_driver: %p\n", node)); + + if (node == NULL) + return B_BAD_VALUE; + + benaphore_lock(&gNodeLock); + + if (node->loading > 0 || node->load_count == 1) { + // we may have to unload the driver because: + // - load_count will be zero after decrement, or + // - there is a concurrent load/unload call, so we + // don't know what the future load_count will be + + // signal active unload; + // don't decrease load_count - the driver is still loaded! + ++node->loading; + + // wait for concurrent load/unload/notification + pnp_start_hook_call_nolock(node); + + if (node->load_count == 1) { + // node is still to be unloaded + benaphore_unlock(&gNodeLock); + + TRACE(("unloading %p\n", node)); + res = unload_driver_int(node); + + benaphore_lock(&gNodeLock); + } else { + // concurrent load/unload either unloaded the driver or + // increased load_count so unload is not necessary or forbidden + TRACE(("don't unload as current load_count is %d\n", + node->load_count)); + res = B_OK; + } + + // signal unload finished + --node->loading; + + if (res == B_OK) { + // everything is fine, so decrease load_count ... + --node->load_count; + // ... and reference count + pnp_remove_node_ref_nolock(node); + } else { + // unloading failed: leave loaded in memory forever + // as load_count is not decreased, the driver will never + // be unloaded, and as reference count is not decreased, + // the node will exist forever + TRACE(("Unload failed - leaving driver of %p in memory\n", node)); + } + + // unblock others + pnp_finish_hook_call_nolock(node); + } else { + // no concurrent load/unload and load_count won't reach zero + --node->load_count; + // each load increased reference count by one - time to undo that + pnp_remove_node_ref_nolock(node); + + res = B_OK; + } + + benaphore_unlock(&gNodeLock); + + return res; +} + diff --git a/src/kernel/core/device_manager/notifications.c b/src/kernel/core/device_manager/notifications.c new file mode 100644 index 0000000000..bb23a7dd29 --- /dev/null +++ b/src/kernel/core/device_manager/notifications.c @@ -0,0 +1,224 @@ +/* +** Copyright 2002-04, Thomas Kurschel. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +*/ + +/* + Part of PnP Manager + + Execution and synchronization of driver hook calls. +*/ + + +#include "device_manager_private.h" + +#include + +#include +#include + + +#define TRACE_NOTIFICATIONS +#ifdef TRACE_NOTIFICATIONS +# define TRACE(x) dprintf x +#else +# define TRACE(x) ; +#endif + + +/** notify a consumer that a device he might handle is added + * consumer_name - module name (!) of consumer + */ + +status_t +pnp_notify_probe_by_module(pnp_node_info *node, const char *consumer_name) +{ + pnp_driver_info *consumer; + status_t res; + + TRACE(("pnp_notify_probe_by_module(node %p, consumer: %s)\n", node, consumer_name)); + + res = get_module(consumer_name, (module_info **)&consumer); + if (res < B_OK) { + dprintf("Cannot load driver module %s (%s)\n", consumer_name, strerror(res)); + goto exit; + } + + if (consumer->probe == NULL) { + dprintf("Driver %s has no probe hook\n", consumer_name); + res = B_ERROR; + } else + res = consumer->probe(node); + + put_module(consumer_name); + +exit: +#ifdef TRACE_NOTIFICATIONS + if (res != B_OK) + dprintf("%s\n", strerror(res)); +#endif + + return res; +} + + +/** notify driver that it's device got removed */ + +static status_t +notify_device_removed(pnp_node_info *node) +{ + bool loaded; + char *module_name; + pnp_driver_info *driver; + void *cookie; + status_t res; + + res = pnp_get_attr_string(node, PNP_DRIVER_DRIVER, &module_name, false); + if (res != B_OK) + return res; + + TRACE(("notify_device_removed(node: %p, consumer: %s)\n", node, module_name)); + + // block concurrent load/unload calls + pnp_start_hook_call(node); + + // don't notify driver if it doesn't know that the device was + // published + if (!node->init_finished) + goto skip; + + // don't take node->loading into account - we want to know + // whether driver is loaded, not whether it is about to get loaded + loaded = node->load_count > 0; + + if (loaded) { + TRACE(("Already loaded\n")); + driver = node->driver; + cookie = node->cookie; + } else { + TRACE(("Not loaded yet\n")); + + // driver is not loaded - load it temporarily without + // calling init_device and pass =NULL to tell + // the driver about + res = get_module(module_name, (module_info **)&driver); + if (res < B_OK) + goto err; + + cookie = NULL; + } + + // this hook is optional, so ignore it silently if NULL + if (driver->device_removed != NULL) + driver->device_removed(node, cookie); + else + dprintf("Driver %s has no device_removed hook\n", module_name); + + // if driver wasn't loaded, call its cleanup method + if (!loaded) { + // again: this hook is optional + if (driver->device_cleanup != NULL) + driver->device_cleanup(node); + } + + if (!loaded) + put_module(module_name); + +skip: + pnp_finish_hook_call(node); + free(module_name); + return B_OK; + +err: + free(module_name); + return res; +} + + +/** notify all drivers in that their node got removed */ + +void +pnp_notify_unregistration(pnp_node_info *notify_list) +{ + pnp_node_info *node; + + TRACE(("pnp_notify_unregistration()\n")); + + for (node = notify_list; node; node = node->notify_next) + notify_device_removed(node); +} + + +/** start driver hook call; must be called before a + * load/unload/notification hook is called + */ + +void +pnp_start_hook_call(pnp_node_info *node) +{ + bool blocked; + + benaphore_lock(&gNodeLock); + + blocked = ++node->num_waiting_hooks > 1; + + benaphore_unlock(&gNodeLock); + + if (blocked) { + TRACE(("Hook call of %p is blocked\n", node)); + + acquire_sem(node->hook_sem); + } +} + + +/** start driver hook call; same pnp_start_hook_call(), but + * node_lock must be hold + */ + +void +pnp_start_hook_call_nolock(pnp_node_info *node) +{ + if (++node->num_waiting_hooks > 1) { + benaphore_unlock(&gNodeLock); + + TRACE(("Hook call of %p is blocked\n", node)); + acquire_sem(node->hook_sem); + + benaphore_lock(&gNodeLock); + } +} + + +/** finish driver hook call; must be called after a load/unload/notification + * hook is called + */ + +void +pnp_finish_hook_call(pnp_node_info *node) +{ + benaphore_lock(&gNodeLock); + + if (--node->num_waiting_hooks > 1) { + TRACE(("Releasing other hook calls of %p\n", node)); + + release_sem(node->hook_sem); + } + + benaphore_unlock(&gNodeLock); +} + + +/** finish driver hook call; same pnp_finish_hook_call(), but + * node_lock must be hold + */ + +void +pnp_finish_hook_call_nolock(pnp_node_info *node) +{ + if (--node->num_waiting_hooks > 1) { + TRACE(("Releasing other hook calls of %p\n", node)); + + release_sem(node->hook_sem); + } +} diff --git a/src/kernel/core/device_manager/patterns.c b/src/kernel/core/device_manager/patterns.c new file mode 100644 index 0000000000..20b420ee50 --- /dev/null +++ b/src/kernel/core/device_manager/patterns.c @@ -0,0 +1,241 @@ +/* +** Copyright 2002-04, Thomas Kurschel. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +*/ + +/* + Part of PnP Manager + + Expansion of patterns. + Used to expand consumer/connector/device names etc. +*/ + + +#include "device_manager_private.h" + +#include +#include + +#include +#include +#include + + +#define TRACE_PATTERNS +#ifdef TRACE_PATTERNS +# define TRACE(x) dprintf x +#else +# define TRACE(x) ; +#endif + + +/** expand node attribute + * node - node to get attributes from + * *pattern - name of attribute, ending with "%" (takes care of "^" sequences) + * on return, *patterns points to trailing "%" + * buffer - buffer for name + * dst - string to write content to + * node_lock must be hold + */ + +static status_t +expand_attr(pnp_node_info *node, const char **pattern, char *buffer, char *dst) +{ + pnp_node_attr_info *attr; + const char *str; + int buffer_len; + + // extract attribute name + buffer_len = 0; + + for (str = *pattern; *str; ++str) { + if (*str == '^' && *(str + 1) != 0) + // copy escaped character directly + ++str; + else if (*str == '%') + break; + + buffer[buffer_len++] = *str; + } + + *pattern = str; + + buffer[buffer_len] = '\0'; + + // get attribute content and add it to path + attr = pnp_find_attr_nolock(node, buffer, true, B_ANY_TYPE); + if (attr == NULL) { + dprintf("Reference to unknown attribute %s\n", buffer); + return B_NAME_NOT_FOUND; + } + + switch (attr->attr.type) { + case B_UINT8_TYPE: + sprintf(buffer, "%02x", attr->attr.value.ui8); + strlcat(dst, buffer, PATH_MAX); + break; + case B_UINT16_TYPE: + sprintf(buffer, "%04x", attr->attr.value.ui16); + strlcat(dst, buffer, PATH_MAX); + break; + case B_UINT32_TYPE: + sprintf(buffer, "%08lx", attr->attr.value.ui32); + strlcat(dst, buffer, PATH_MAX); + break; + case B_UINT64_TYPE: + sprintf(buffer, "%16Lx", attr->attr.value.ui64); + strlcat(dst, buffer, PATH_MAX); + break; + case B_STRING_TYPE: { + const char *str; + + strlcat(dst, "\"", PATH_MAX); + + for (str = attr->attr.value.string; *str; ++str) { + char ch; + + ch = *str; + if (ch >= 32 && ch <= 126 && ch != '/' && ch != '%' && ch != '"') { + buffer[0] = ch; + buffer[1] = 0; + } else { + // convert unprintable/forbidden characters to numbers + sprintf(buffer, "%%%u%%", (unsigned char)ch); + } + + strlcat(dst, buffer, PATH_MAX); + } + + strlcat(dst, "\"", PATH_MAX); + break; + } + case B_RAW_TYPE: + default: + benaphore_unlock(&gNodeLock); + return B_BAD_VALUE; + } + + benaphore_unlock(&gNodeLock); + return B_OK; +} + + +/** expand pattern. + * the pattern is both expanded and the position of inidiual parts returned + * pattern - pattern to expand + * dest - expanded name is appended to this string + * buffer - buffer of MAX_PATH+1 size supplied by caller + * term_array - list of lengths of individual sub-names + * with index 0 containing the length of the shortest and + * num_parts-1 - length of the longest sub-name + * and can be NULL + * node_lock must be hold + */ + +status_t +pnp_expand_pattern(pnp_node_info *node, const char *pattern, char *dest, + char *buffer, size_t *term_array, int *num_parts) +{ + const char *str; + int id; + status_t res; + int buffer_len = 0; + + TRACE(("expand_pattern(pattern: %s)\n", pattern)); + + for (str = pattern, id = 0; *str; ++str) { + switch (*str) { + case '^': + // character following "^" is copied directly + // exception: if there is a trailing "^" at end of string, + // we copy it directly + if (*(str + 1) != 0) + ++str; + + buffer[buffer_len++] = *str; + break; + + case '|': + // end of one name part + buffer[buffer_len] = '\0'; + buffer_len = 0; + strlcat(dest, buffer, PATH_MAX); + + TRACE(("%d: %s\n", id, dest)); + + if (term_array != NULL) + term_array[id++] = strlen(dest); + break; + + case '%': + // begin of place-holder + buffer[buffer_len] = '\0'; + buffer_len = 0; + strlcat(dest, buffer, PATH_MAX); + + // skip "%" + ++str; + + res = expand_attr(node, &str, buffer, dest); + if (res != B_OK) + return res; + break; + + default: + // normal character + buffer[buffer_len++] = *str; + } + + // avoid overflow of buffer + if (buffer_len > PATH_MAX) + buffer_len = PATH_MAX; + } + + // append trailing characters + buffer[buffer_len] = 0; + buffer_len = 0; + strlcat(dest, buffer, PATH_MAX); + + if (term_array != NULL) + term_array[id++] = strlen(dest); + + if (num_parts != NULL) + *num_parts = id; + + TRACE(("result: %s\n", dest)); + + return B_OK; +} + + +/** expand pattern as specified by attribute + * node_lock must be hold + */ + +status_t +pnp_expand_pattern_attr(pnp_node_info *node, const char *attr_name, + char **expanded) +{ + const char *pattern; + status_t res; + + TRACE(("pnp_expand_pattern_attr()\n")); + + if (pnp_get_attr_string_nolock(node, attr_name, &pattern, false) != B_OK) { + *expanded = strdup(""); + return B_OK; + } + + *expanded = malloc((PATH_MAX + 1) * 2); + if (*expanded == NULL) + return B_NO_MEMORY; + + **expanded = 0; + res = pnp_expand_pattern(node, pattern, + *expanded, *expanded + PATH_MAX + 1, NULL, NULL); + + if (res != B_OK) + free(*expanded); + + return res; +} diff --git a/src/kernel/core/device_manager/probe.c b/src/kernel/core/device_manager/probe.c new file mode 100644 index 0000000000..a4b78cdebd --- /dev/null +++ b/src/kernel/core/device_manager/probe.c @@ -0,0 +1,571 @@ +/* +** Copyright 2002-04, Thomas Kurschel. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +*/ + +/* + Part of PnP Manager + Probing for consumers. + + Here is all the core logic how consumers are found for one node. +*/ + + +#include "device_manager_private.h" + +#include + +#include +#include +#include +#include +#include + + +#define TRACE_PROBE +#ifdef TRACE_PROBE +# define TRACE(x) dprintf x +#else +# define TRACE(x) ; +#endif + +// ToDo: for now - it won't be necessary to do this later anymore +#define pnp_boot_safe_realpath realpath +#define pnp_boot_safe_lstat lstat +#define pnp_boot_safe_opendir opendir +#define pnp_boot_safe_readdir readdir +#define pnp_boot_safe_closedir closedir + +// list of driver registration directories +const char *pnp_registration_dirs[2] = { + COMMON_DRIVER_REGISTRATION, + SYSTEM_DRIVER_REGISTRATION +}; + + +// list of module directories +static const char *modules_dirs[2] = { + COMMON_MODULES_DIR, + SYSTEM_MODULES_DIR +}; + + +/** notify a consumer that a device he might handle is added + * consumer_name - file name (!) of consumer + * (moved to end of file to avoid inlining) + */ + +static +status_t notify_probe_by_file(pnp_node_info *node, const char *consumer_name) +{ + char *type; + char *resolved_path; + char *module_name; + int i; + bool valid_module_name; + status_t res; + + TRACE(("notify_probe_by_file(%s)\n", consumer_name)); + + res = pnp_get_attr_string(node, PNP_DRIVER_TYPE, &type, false); + if (res != B_OK) + return res; + + // resolve link to actual driver file + resolved_path = malloc(PATH_MAX + 1); + if (resolved_path == NULL) { + res = B_NO_MEMORY; + goto err; + } + + module_name = pnp_boot_safe_realpath(consumer_name, resolved_path); + if (module_name == NULL) { + // broken link or something + dprintf("Cannot resolve driver file name: %s\n", consumer_name); + res = errno; + goto err2; + } + + // make sure both consumer and module file are either in + // system or user modules directory + valid_module_name = false; + + for (i = 0; i < (disable_useraddons ? 1 : 2); ++i) { + int len = strlen(modules_dirs[i]); + + if (!strncmp(consumer_name, modules_dirs[i], len) + && !strncmp(module_name, modules_dirs[i], len)) { + valid_module_name = true; + module_name = module_name + len; + break; + } + } + + if (!valid_module_name) { + TRACE(("Module file %s of consumer %s is in wrong path\n", + consumer_name, module_name)); + res = B_NAME_NOT_FOUND; + goto err2; + } + + // append driver type to get specific module name + strlcat(module_name, "/", PATH_MAX); + strlcat(module_name, type, PATH_MAX); + + res = pnp_notify_probe_by_module(node, module_name); + +err2: + free(resolved_path); +err: + free(type); + return res; +} + + +/** compose all possible names of Specific drivers; as there are + * multiple names which only differ in length, the most specific + * driver name gets stored in , whereas is a + * list of lengths of individual driver names with index 0 + * containing the length of the shortest and num_parts-1 the + * length of the longest name; is a supplied buffer of + * MAX_PATH+1 size + */ + +static status_t +compose_driver_names(pnp_node_info *node, const char *dir, + const char *filename_pattern, int num_parts, + char *path, char *buffer, size_t **res_term_array) +{ + size_t *term_array; + int id; + status_t res; + + term_array = malloc(num_parts * sizeof(size_t)); + if (term_array == NULL) + return B_NO_MEMORY; + + strlcpy(path, dir, PATH_MAX); + strlcat(path, "/", PATH_MAX); + + TRACE(("compose_drive_names(%s)\n", path)); + + benaphore_lock(&gNodeLock); + + res = pnp_expand_pattern(node, filename_pattern, path, buffer, + term_array, &id); + + benaphore_unlock(&gNodeLock); + + if (res != B_OK) + goto err; + + if (id != num_parts) { + panic("compose_driver_names: number of pattern parts in %s is inconsistent (%d!=%d)", + filename_pattern, num_parts, id); + } + + TRACE(("driver=%s, parts=%d\n", buffer, id)); + + *res_term_array = term_array; + return B_OK; + +err: + free(term_array); + return res; +} + + +/** notify all drivers under . If is true, notify all, + * if false, stop once a drivers notification function returned B_OK + * buffer - scratch buffer of size PATH_MAX + 1 ; destroyed on exit + * return: B_NAME_NOT_FOUND, if tell_all is false and no driver returned B_OK + */ + +static status_t +try_drivers(pnp_node_info *node, char *directory, + bool tell_all, char *buffer) +{ + DIR *dir; + size_t dir_len; + struct dirent *entry; + int i; + + TRACE(("try_drivers(dir: %s)\n", directory)); + + // first try user drivers, then system drivers + for (i = 0; i < (disable_useraddons ? 1 : 2); ++i) { + strcpy(buffer, pnp_registration_dirs[i]); + strlcat(buffer, directory, PATH_MAX); + + TRACE(("cur_dir: %s\n", buffer)); + + dir_len = strlen(buffer); + + dir = pnp_boot_safe_opendir(buffer); + if (dir == NULL) { + //SHOW_ERROR(3, "Directory %s doesn't exists", buffer); + // directory doesn't exist + return tell_all ? B_OK : B_NAME_NOT_FOUND; + } + + while ((entry = pnp_boot_safe_readdir(dir)) != NULL) { + buffer[dir_len] = 0; + strlcat(buffer, "/", PATH_MAX); + strlcat(buffer, entry->d_name, PATH_MAX); + + // skip default directory entries + if (!strcmp( entry->d_name, ".") + || !strcmp( entry->d_name, "..")) + continue; + + if (notify_probe_by_file( node, buffer) == B_OK && !tell_all) { + // tell_all is false, return on first hit + pnp_boot_safe_closedir(dir); + return B_OK; + } + } + + pnp_boot_safe_closedir(dir); + } + + return tell_all ? B_OK : B_NAME_NOT_FOUND; +} + + +/** find normal consumer of node that are stored under ; first, we + * look for a specific driver; if none could be found, find a generic + * one path, buffer - used as scratch buffer (all of size PATH_MAX + 1) + * return: B_NAME_NOT_FOUND if no consumer could be found + */ + +static status_t +find_normal_consumer(pnp_node_info *node, const char *dir, + const char *filename_pattern, int num_parts, + char *path, char *buffer, bool *found_normal_driver) +{ + status_t res; + size_t *term_array; + int i; + + // don't search for specific consumers if there is only a directory given + if (*filename_pattern) { + res = compose_driver_names(node, dir, filename_pattern, num_parts, + path, buffer, &term_array); + if (res != B_OK) + return res; + + // try to find specific driver, starting with most specific, i.e. + // the one with the longest name + for (i = num_parts - 1; i >= 0; --i) { + int j; + + TRACE(("%d: %lu\n", i, term_array[i])); + path[term_array[i]] = 0; + + // first, check for user driver, then system driver + for (j = 0; j < (disable_useraddons ? 1 : 2); ++j) { + struct stat dummy; + + strcpy(buffer, pnp_registration_dirs[j]); + strlcat(buffer, path, PATH_MAX); + + // do a stat to avoid error message if Specific Driver + // isn't provided + if (pnp_boot_safe_lstat(buffer, &dummy) == 0) { + res = notify_probe_by_file(node, buffer); + if (res == B_OK) + // got him! + break; + } else { + /*SHOW_ERROR( 4, "Specific driver %s doesn't exists", + buffer );*/ + } + } + } + + free(term_array); + + if (i >= 0) { + // found specific consumer + *found_normal_driver = true; + return B_OK; + } + } + + // no specific consumer - go through generic driver + strlcpy(path, dir, PATH_MAX); + strlcat(path, GENERIC_SUBDIR, PATH_MAX); + + if (try_drivers(node, path, false, buffer) != B_OK) { + *found_normal_driver = false; + return B_NAME_NOT_FOUND; + } + + *found_normal_driver = true; + return B_OK; +} + + +/** Notify fixed consumers that device was added; in contrast to dynamic + * consumers, errors reported by fixed consumers are not ignored but + * returned. + */ + +status_t +pnp_notify_fixed_consumers(pnp_node_info *node) +{ + int i; + char *buffer; + + TRACE(("pnp_notify_fixed_consumers()\n")); + + buffer = malloc(PATH_MAX + 1); + if (buffer == NULL) + return B_NO_MEMORY; + + // first, append nothing, then "/0", "/1" etc. + for (i = -1; ; ++i) { + char *consumer; + + strcpy(buffer, PNP_DRIVER_FIXED_CONSUMER); + if (i >= 0) + sprintf(buffer + strlen(buffer), "/%d", i); + + // if no more fixed consumers, cancel loop silently + if (pnp_get_attr_string( node, buffer, &consumer, false) != B_OK) + break; + + TRACE(("Consumer %d: %s\n", i, consumer)); + + if (pnp_notify_probe_by_module(node, consumer) != B_OK) { + dprintf("Cannot notify fixed consumer %s\n", consumer); + + // report error if fixed consumers couldn't be loaded + // as they are obviously crucial (else they wouldn't be fixed) + free(consumer); + free(buffer); + + return B_NAME_NOT_FOUND; + } + + free(consumer); + } + + free(buffer); + return B_OK; +} + + +/** pre-process dynamic consumer name pattern. + * split into directory and pattern and count split positions; + * further, remove quotes from directory + * pattern - pattern of consumer name + * buffer - buffer to store results in + * (returned strings all point to !) + * dir - directory + * filename_pattern - pattern of file name + * *num_parts - number of split positions + */ + +static status_t +preprocess_consumer_names(const char *pattern, char *buffer, + char **dir, char **filename_pattern, int *const num_parts) +{ + char *last_slash, *str, *dest; + bool parts_began; + + // make a copy of pattern as we will strip escapes from directory part + strlcpy(buffer, pattern, PATH_MAX); + + // find directory part and count splitpoints + parts_began = false; + last_slash = NULL; + *num_parts = 1; + + for (str = buffer; *str; ++str) { + switch (*str) { + case '^': + // honour escaped characters, taking care of trailing escape + if (*(str + 1)) + ++str; + break; + + case '|': + ++*num_parts; + parts_began = true; + break; + + case '%': + ++str; + + // find end of attribute name, taking care of escape sequences + for (; *str != 0; ++str) { + if (*str == '^') { + if (*(str + 1) != 0) + ++str; + } else if (*str == '%') + break; + } + + if (*str == 0) { + dprintf("missing matching '%%' in consumer pattern %s\n", pattern); + return B_BAD_VALUE; + } + break; + + case '/': + // directory finishes at last "/" before first "|" + if (!parts_began) + last_slash = str; + break; + } + } + + if (last_slash == 0) { + dprintf("missing directory in consumer pattern %s\n", pattern); + return B_BAD_VALUE; + } + + // split into directory and filename pattern in place + *dir = buffer; + *last_slash = 0; + *filename_pattern = last_slash + 1; + + // remove escape sequences from directory + for (str = buffer, dest = buffer; *str; ++str) { + if (*str == '^' && (str + 1) != 0) + ++str; + + *dest++ = *str; + } + + *dest = 0; + + TRACE(("dir=%s, filename_pattern=%s, num_parts=%d\n", + *dir, *filename_pattern, *num_parts)); + + return B_OK; +} + + +/** find consumers for one given pattern + * has_normal_drivers - in: true - don't search for specific driver + * out: true - specific driver was found + */ + +static status_t +notify_dynamic_consumer(pnp_node_info *node, const char *pattern, + bool *has_normal_driver) +{ + status_t res; + char *buffers; + char *dir, *filename_pattern; + int num_parts; + + TRACE(("notify_dynamic_consumer(pattern=%s, has_normal_driver=%d)\n", + pattern, *has_normal_driver)); + + // we need three buffers - allocate them at once for simpliness + buffers = malloc(3 * (PATH_MAX + 1)); + if (buffers == NULL) + return B_NO_MEMORY; + + res = preprocess_consumer_names(pattern, buffers + 2 * (PATH_MAX + 1), + &dir, &filename_pattern, &num_parts); + if (res < B_OK) + goto err; + + if (!*has_normal_driver) { + // find specific/generic consumer + res = find_normal_consumer(node, dir, filename_pattern, num_parts, + buffers, buffers + PATH_MAX + 1, has_normal_driver); + if (res != B_OK && res != B_NAME_NOT_FOUND) + // only abort if there was a "real" problem; + // if there is no specific/generic consumer, we don't bother + // (having no driver is not funny but happens) + goto err; + } + + // tell universal drivers + strlcpy(buffers, dir, PATH_MAX); + strlcat(buffers, UNIVERSAL_SUBDIR, PATH_MAX); + + res = try_drivers(node, buffers, true, buffers + PATH_MAX + 1); + if (res != B_OK && res != B_NAME_NOT_FOUND) + // again, only abort on real problems + goto err; + + free(buffers); + + TRACE(("done\n")); + return B_OK; + +err: + free(buffers); + return res; +} + + +/** find and notify dynamic consumers that device was added + * errors returned by consumers aren't reported, only problems + * like malformed consumer patterns + */ + +status_t +pnp_notify_dynamic_consumers(pnp_node_info *node) +{ + int i; + char *buffer; + status_t res; + bool has_normal_driver = false; + + TRACE(("pnp_notify_dynamic_consumers()\n")); + + buffer = malloc(PATH_MAX + 1); + if (buffer == NULL) + return B_NO_MEMORY; + + //pnp_load_boot_links(); + + // first, append nothing, then "/0", "/1" etc. + for (i = -1; ; ++i) { + char *consumer; + + strcpy(buffer, PNP_DRIVER_DYNAMIC_CONSUMER); + if (i >= 0) + sprintf(buffer + strlen( buffer ), "/%d", i); + + // if no more dynamic consumers, cancel loop silently + if (pnp_get_attr_string(node, buffer, &consumer, false) != B_OK) { + // starting with .../0 is OK, so ignore error if i=-1 + if (i == -1) + continue; + else + break; + } + + TRACE(("Consumer pattern %d: %s\n", i, consumer)); + + res = notify_dynamic_consumer(node, consumer, &has_normal_driver); + + free(consumer); + + if (res != B_OK) { + // this is only reached if a serious error occured, + // see notify_dynamic_consumer() + goto err; + } + } + + free(buffer); + //pnp_unload_boot_links(); + + return B_OK; + +err: + free(buffer); + //pnp_unload_boot_links(); + + return res; +} + diff --git a/src/kernel/core/device_manager/scan.c b/src/kernel/core/device_manager/scan.c new file mode 100644 index 0000000000..9bcb0191f7 --- /dev/null +++ b/src/kernel/core/device_manager/scan.c @@ -0,0 +1,445 @@ +/* +** Copyright 2002-04, Thomas Kurschel. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +*/ + +/* + Part of PnP Manager + + Scanning for consumers. + + Logic to execute (re)scanning of nodes. + + During initial registration, problems with fixed consumers are + fatal. During rescan, this is ignored + (TODO: should this be done more consistently?) +*/ + + +#include "device_manager_private.h" +#include "pnp_bus.h" + +#include +#include + + +#define TRACE_SCAN +#ifdef TRACE_SCAN +# define TRACE(x) dprintf x +#else +# define TRACE(x) ; +#endif + + +/** public function: rescan PnP node */ + +status_t +pnp_rescan(pnp_node_info *node, uint depth) +{ + return pnp_rescan_int(node, depth, false); +} + + +/** execute registration of fully constructed node */ + +status_t +pnp_initial_scan(pnp_node_info *node) +{ + pnp_start_hook_call(node); + + node->init_finished = true; + + pnp_finish_hook_call(node); + + return pnp_rescan_int(node, 1, false); +} + + +/** check whether rescanning a node makes sense in terms of + * double scans. + * node_lock must be hold + */ + +static bool +is_rescan_sensible(pnp_node_info *node) +{ + uint depth; + pnp_node_info *child; + + TRACE(("is_rescan_sensible(node: %p)\n", node)); + + // if a child is being scanned, abort our rescan to avoid + // concurrent scans by the same driver on the same device + for (child = node->children; child != NULL; child = child->children_next) { + if (child->rescan_depth > 0) { + TRACE(("child %p is being scanned\n", child)); + return false; + } + } + + TRACE(("check parents\n")); + + // make sure neither this nor parent node is scanned recursively + // up to this node as this would lead to unnecessary double-scans + // (don't need to check whether parent is unregistered as + // all children would have been unregistered immediately) + for (depth = 1; node != NULL; node = node->parent, ++depth) { + if (node->rescan_depth >= depth) { + TRACE(("parent %p is being scanned", node)); + return false; + } + } + + return true; +} + + +/** mark children of node as being scanned. + * children that have their drivers loaded are not + * scanned unless they allow that explicitely. + * node_lock must be hold + */ + +static void +mark_children_scanned(pnp_node_info *node) +{ + pnp_node_info *child, *next_child; + + TRACE(("mark_children_scanned()\n")); + + for (child = node->children; child != NULL; child = next_child) { + uint8 never_rescan, no_live_rescan; + + next_child = child->children_next; + + // ignore dead children + if (!child->registered) + continue; + + // check whether device can be rescanned at all + if (pnp_get_attr_uint8_nolock(child, PNP_DRIVER_NEVER_RESCAN, + &never_rescan, false) == B_OK && never_rescan) { + TRACE(("no rescan allowed on device %p\n", child)); + continue; + } + + if (pnp_get_attr_uint8_nolock(child, PNP_DRIVER_NO_LIVE_RESCAN, + &no_live_rescan, false) != B_OK) + no_live_rescan = false; + + if (no_live_rescan) { + TRACE(("no life rescan allowed on device %p\n", child)); + + if (child->load_count + child->loading > 0) { + TRACE(("device %p loaded - skipping it\n", child)); + continue; + } + + child->blocked_by_rescan = true; + ++child->load_block_count; + } + + child->verifying = true; + child->redetected = false; + + // unload driver during rescan if needed + // (need to lock next_child temporarily as we release node_lock) + if (next_child) + ++next_child->ref_count; + + benaphore_unlock(&gNodeLock); + + pnp_unload_driver_automatically(child, true); + + benaphore_lock(&gNodeLock); + + if (next_child) + pnp_remove_node_ref_nolock(next_child); + } +} + + +/** stop children verification, resetting associated flag and unblocking loads. */ + +static void +reset_children_verification(pnp_node_info *node) +{ + pnp_node_info *child, *next_child; + + TRACE(("reset_children_verification()\n")); + + benaphore_lock(&gNodeLock); + + for (child = node->children; child != NULL; child = next_child) { + next_child = child->children_next; + + if (!child->verifying) + continue; + + child->verifying = false; + + if (child->blocked_by_rescan) { + child->blocked_by_rescan = false; + pnp_unblock_load(child); + } + + if (next_child) + ++next_child->ref_count; + + benaphore_unlock(&gNodeLock); + + pnp_load_driver_automatically(node, true); + + benaphore_lock(&gNodeLock); + + if (next_child) + pnp_remove_node_ref_nolock(next_child); + } + + benaphore_unlock(&gNodeLock); +} + + +/** unregister child nodes that weren't detected anymore */ + +static void +unregister_lost_children(pnp_node_info *node) +{ + pnp_node_info *child, *dependency_list; + + TRACE(("unregister_lost_children()\n")); + + benaphore_lock(&gNodeLock); + + for (child = node->children, dependency_list = NULL; child != NULL; + child = child->children_next) { + if (child->verifying && !child->redetected) { + TRACE(("removing lost device %p\n", child)); + // child wasn't detected anymore - put it onto remove list + pnp_unregister_node_rec(child, &dependency_list); + } + } + + benaphore_unlock(&gNodeLock); + + // finish verification of children + // (must be done now to unblock them; + // else we risk deadlocks during notification) + reset_children_verification(node); + + // inform all drivers of unregistered nodes + pnp_notify_unregistration(dependency_list); + + // now, we can safely decrease ref_count of unregistered nodes + pnp_unref_unregistered_nodes(dependency_list); +} + + +/** recursively scan children of node (using depth-scan). + * node_lock must be hold + */ + +static status_t +recursive_scan(pnp_node_info *node, uint depth) +{ + pnp_node_info *child, *next_child; + status_t res; + + TRACE(("recursive_scan(node: %p, depth=%d)\n", node, depth)); + + child = node->children; + + // the child we want to access must be locked + if (child != NULL) + ++child->ref_count; + + for (; child != NULL; child = next_child) { + next_child = child->children_next; + + // lock next child, so its node is still valid after rescan + if (next_child != NULL) + ++next_child->ref_count; + + // during rescan, we must release node_lock to not deadlock + benaphore_unlock(&gNodeLock); + + res = pnp_rescan(child, depth - 1); + + benaphore_lock(&gNodeLock); + + // unlock current child as it's not accessed anymore + pnp_remove_node_ref_nolock(child); + + // ignore errors because of touching unregistered nodes + if (res != B_OK && res != B_NAME_NOT_FOUND) + goto err; + } + + // no need unlock last child (don't be loop already) + TRACE(("recursive_scan(): done\n")); + return B_OK; + +err: + if (next_child != NULL) + pnp_remove_node_ref_nolock(next_child); + + TRACE(("recursive_scan(): failed\n")); + return res; +} + + +/** ask bus to (re-)scan for connected devices */ + +static void +rescan_bus(pnp_node_info *node) +{ + // busses can register their children themselves + pnp_bus_info *interface; + void *cookie; + bool defer_probe; + uint8 defer_probe_var; + + // delay children probing if requested + defer_probe = pnp_get_attr_uint8(node, + PNP_BUS_DEFER_PROBE, &defer_probe_var, false ) == B_OK + && defer_probe_var != 0; + + if (defer_probe) { + TRACE(("defer probing for consumers\n")); + pnp_defer_probing_of_children(node); + } + + // load driver during rescan + pnp_load_driver(node, NULL, (pnp_driver_info **)&interface, &cookie); + + TRACE(("scan bus\n")); + + // block other notifications (currently, only "removed" could be + // called as driver is/will stay loaded and scanning the bus + // concurrently has been assured to not happen) + pnp_start_hook_call(node); + + if (interface->rescan != NULL) + interface->rescan(cookie); + + pnp_finish_hook_call(node); + + pnp_unload_driver(node); + + // time to execute delayed probing + if (defer_probe) + pnp_probe_waiting_children(node); +} + + +/** rescan for consumers + * ignore_fixed_consumers - true, to leave fixed consumers alone + */ + +status_t +pnp_rescan_int(pnp_node_info *node, uint depth, + bool ignore_fixed_consumers) +{ + bool is_bus; + uint8 dummy; + status_t res = B_OK; + + TRACE(("pnp_rescan_int(node: %p, depth=%d)\n", node, depth)); + + //pnp_load_boot_links(); + + // ignore depth 0 silently + if (depth == 0) { + res = B_OK; + goto err3; + } + + is_bus = pnp_get_attr_uint8(node, PNP_BUS_IS_BUS, &dummy, false) == B_OK; + + benaphore_lock(&gNodeLock); + + // don't scan unregistered nodes + if (!node->registered) { + TRACE(("node not registered")); + res = B_NAME_NOT_FOUND; + goto err2; + } + + if (!is_rescan_sensible(node)) { + // somebody else does the rescan, so no need to report error + TRACE(("rescan not sensible\n")); + res = B_OK; + goto err2; + } + + // mark old devices + // exception: during registration, fixed consumers are + // initialized seperately, and we don't want to mark their node + // as being old + if (!ignore_fixed_consumers || is_bus) + mark_children_scanned(node); + + // tell other scans what we are doing to avoid double scans + node->rescan_depth = depth; + + // keep node alive + ++node->ref_count; + + benaphore_unlock(&gNodeLock); + + // do the real thing - scan node + if (is_bus) + rescan_bus(node); + + // ask possible consumers to register their nodes + if (!ignore_fixed_consumers) { + TRACE(("scan fixed consumers\n")); + + res = pnp_notify_fixed_consumers(node); + if (res != B_OK) + goto err; + } + + res = pnp_notify_dynamic_consumers(node); + if (res != B_OK) + goto err; + + // unregister children that weren't detected anymore + unregister_lost_children(node); + + benaphore_lock(&gNodeLock); + + // mark scan as being finished to not block recursive scans + node->rescan_depth = 0; + + // scan children recursively; + // keep the node_lock to make sure noone removes children meanwhile + if (depth > 1) + res = recursive_scan(node, depth); + + pnp_remove_node_ref_nolock(node); + benaphore_unlock(&gNodeLock); + + //pnp_unload_boot_links(); + + TRACE(("pnp_rescan_int(): done (%p) - %s\n", node, strerror(res))); + return res; + +err: + reset_children_verification(node); + + benaphore_lock(&gNodeLock); + + node->rescan_depth = 0; + pnp_remove_node_ref_nolock(node); + +err2: + benaphore_unlock(&gNodeLock); + + TRACE(("pnp_rescan_int(): failed (%p, %s)\n", node, strerror(res))); + +err3: + //pnp_unload_boot_links(); + + return res; +}