9c4f4c037d
Minor cleanup. git-svn-id: file:///srv/svn/repos/haiku/trunk/current@10673 a95241bf-73f2-0310-859d-f6bbb57e9c96
497 lines
12 KiB
C
497 lines
12 KiB
C
/*
|
|
* Copyright 2004-2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
|
|
* Copyright 2002-2004, Thomas Kurschel. All rights reserved.
|
|
*
|
|
* Distributed under the terms of the MIT License.
|
|
*/
|
|
|
|
/*
|
|
Part of Device Manager
|
|
|
|
Device Node Registration.
|
|
*/
|
|
|
|
|
|
#include "device_manager_private.h"
|
|
#include "dl_list.h"
|
|
|
|
#include <KernelExport.h>
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
|
|
#define TRACE_REGISTRATION
|
|
#ifdef TRACE_REGISTRATION
|
|
# define TRACE(x) dprintf x
|
|
#else
|
|
# define TRACE(x) ;
|
|
#endif
|
|
|
|
|
|
// decrease ref_count of unregistered nodes, specified by <node_list>
|
|
|
|
void
|
|
pnp_unref_unregistered_nodes(device_node_info *node_list)
|
|
{
|
|
device_node_info *node, *next_node;
|
|
|
|
for (node = node_list; node; node = next_node) {
|
|
next_node = node->notify_next;
|
|
|
|
pnp_remove_node_ref(node);
|
|
}
|
|
}
|
|
|
|
|
|
// mark node being registered, so it can be accessed via load_driver()
|
|
|
|
static status_t
|
|
mark_node_registered(device_node_info *node)
|
|
{
|
|
status_t res;
|
|
|
|
TRACE(("mark_node_registered(%p)\n", node));
|
|
|
|
benaphore_lock(&gNodeLock);
|
|
|
|
if (node->parent && !node->parent->registered) {
|
|
TRACE(("Cannot mark registered: parent is already unregistered\n"));
|
|
res = B_NOT_ALLOWED;
|
|
} else {
|
|
res = B_OK;
|
|
node->registered = true;
|
|
++node->ref_count;
|
|
}
|
|
|
|
benaphore_unlock(&gNodeLock);
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
// check whether device is redetected and remove
|
|
// any device that is on same connection.
|
|
// *redetected is set true if device is already registered;
|
|
// the node must not yet be linked into parent node's children list
|
|
|
|
static status_t
|
|
is_device_redetected(device_node_info *node, device_node_info *parent, bool *redetected)
|
|
{
|
|
device_node_info *sibling;
|
|
status_t res;
|
|
bool found = false;
|
|
bool same_device = false;
|
|
bool unregister_sibling;
|
|
|
|
TRACE(("Checking %p, parent %p\n", node, parent));
|
|
|
|
*redetected = false;
|
|
|
|
if (parent == NULL) {
|
|
TRACE(("done\n"));
|
|
return false;
|
|
}
|
|
|
|
// we keep the lock very long, but this is the only way to be sure that
|
|
// noone else (un)-registers a conflicting note during the tests
|
|
benaphore_lock(&gNodeLock);
|
|
|
|
{
|
|
char *connection, *device_identifier;
|
|
|
|
// get our connection and device id...
|
|
if (pnp_expand_pattern_attr(node, PNP_DRIVER_CONNECTION, &connection) != B_OK)
|
|
connection = strdup("");
|
|
|
|
if (pnp_expand_pattern_attr(node, PNP_DRIVER_DEVICE_IDENTIFIER,
|
|
&device_identifier) != B_OK)
|
|
device_identifier = strdup("");
|
|
|
|
TRACE(("connection: %s, device_identifier: %s\n", connection, device_identifier));
|
|
|
|
// ...and compare it with our siblings
|
|
for (sibling = parent->children; sibling != NULL; sibling = sibling->siblings_next) {
|
|
char *sibling_connection, *sibling_device_identifier;
|
|
bool same_connection;
|
|
|
|
// ignore dead siblings
|
|
if (!sibling->registered)
|
|
break;
|
|
|
|
TRACE(("%p\n", sibling));
|
|
|
|
if (pnp_expand_pattern_attr(sibling, PNP_DRIVER_CONNECTION,
|
|
&sibling_connection) != B_OK)
|
|
sibling_connection = strdup("");
|
|
|
|
same_connection = !strcmp(connection, sibling_connection);
|
|
free(sibling_connection);
|
|
|
|
if (!same_connection)
|
|
continue;
|
|
|
|
// found a device on same connection - check whether it's the same device
|
|
found = true;
|
|
|
|
if (pnp_expand_pattern_attr(sibling, PNP_DRIVER_DEVICE_IDENTIFIER,
|
|
&sibling_device_identifier) != B_OK)
|
|
sibling_device_identifier = strdup("");
|
|
|
|
TRACE(("device %s is on same connection\n", sibling_device_identifier));
|
|
|
|
same_device = !strcmp(device_identifier, sibling_device_identifier);
|
|
free(sibling_device_identifier);
|
|
break;
|
|
}
|
|
|
|
free(connection);
|
|
free(device_identifier);
|
|
}
|
|
|
|
if (found) {
|
|
TRACE(("found device on same connection\n"));
|
|
if (!same_device) {
|
|
// there is a different device on the same connection -
|
|
// kick out the old device
|
|
TRACE(("different device was detected\n"));
|
|
|
|
*redetected = false;
|
|
unregister_sibling = true;
|
|
} else {
|
|
const char *driver_name, *old_driver_name;
|
|
const char *driver_type, *old_driver_type;
|
|
|
|
TRACE(("it's the same device\n"));
|
|
|
|
// found same device on same connection - make sure it's still the same driver
|
|
if (pnp_get_attr_string_nolock(node, PNP_DRIVER_DRIVER, &driver_name, false) != B_OK
|
|
|| pnp_get_attr_string_nolock(sibling, PNP_DRIVER_DRIVER, &old_driver_name, false) != B_OK
|
|
|| pnp_get_attr_string_nolock(node, PNP_DRIVER_TYPE, &driver_type, false) != B_OK
|
|
|| pnp_get_attr_string_nolock(sibling, PNP_DRIVER_TYPE, &old_driver_type, false) != B_OK) {
|
|
// these attributes _must_ be there, so this cannot happen
|
|
// (but we are prepared)
|
|
res = B_ERROR;
|
|
goto err;
|
|
}
|
|
|
|
if (strcmp(driver_name, old_driver_name) != 0
|
|
|| strcmp(driver_type, old_driver_type) != 0) {
|
|
// driver has changed - replace device node
|
|
TRACE(("driver got replaced\n"));
|
|
|
|
*redetected = false;
|
|
unregister_sibling = true;
|
|
} else {
|
|
// it's the same driver for the same device
|
|
TRACE(("redetected device\n"));
|
|
|
|
*redetected = true;
|
|
unregister_sibling = false;
|
|
}
|
|
}
|
|
} else {
|
|
// no collision on the device's connection
|
|
TRACE(("no collision\n"));
|
|
|
|
*redetected = false;
|
|
unregister_sibling = false;
|
|
}
|
|
|
|
if (*redetected && sibling->verifying)
|
|
sibling->redetected = true;
|
|
|
|
if (unregister_sibling) {
|
|
// increase ref_count to make sure node still exists
|
|
// when node_lock has been released
|
|
++sibling->ref_count;
|
|
benaphore_unlock(&gNodeLock);
|
|
|
|
pnp_unregister_device(sibling);
|
|
pnp_remove_node_ref_nolock(sibling);
|
|
} else
|
|
benaphore_unlock(&gNodeLock);
|
|
|
|
return B_OK;
|
|
|
|
err:
|
|
benaphore_unlock(&gNodeLock);
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
// postpone searching for consumers if necessary
|
|
// return: true, if postponed
|
|
|
|
static bool
|
|
pnp_postpone_probing(device_node_info *node)
|
|
{
|
|
benaphore_lock(&gNodeLock);
|
|
|
|
// ask parent(!) if probing is to be postponed
|
|
if (node->parent == NULL || !node->parent->defer_probing) {
|
|
benaphore_unlock(&gNodeLock);
|
|
return false;
|
|
}
|
|
|
|
// yes: this happens if the new node is a child of a bus node whose
|
|
// rescan has not been finished
|
|
TRACE(("postpone probing of node %p\n", node));
|
|
|
|
ADD_DL_LIST_HEAD(node, node->parent->unprobed_children, unprobed_ );
|
|
benaphore_unlock(&gNodeLock);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// public: register device node.
|
|
// in terms of I/O resources: if registration fails, they are freed; reason is
|
|
// that they may have been transferred to node before error and back-transferring
|
|
// them would be complicated
|
|
|
|
status_t
|
|
pnp_register_device(device_node_handle parent, const device_attr *attrs,
|
|
const io_resource_handle *io_resources, device_node_handle *node)
|
|
{
|
|
device_node_info *node_inf;
|
|
bool redetected;
|
|
status_t res = B_OK;
|
|
|
|
res = pnp_alloc_node(attrs, io_resources, &node_inf);
|
|
if (res != B_OK)
|
|
goto err;
|
|
|
|
{
|
|
char *driver_name, *type;
|
|
|
|
if (pnp_get_attr_string(node_inf, PNP_DRIVER_DRIVER, &driver_name, false) != B_OK) {
|
|
dprintf("Missing driver filename in node\n");
|
|
res = B_BAD_VALUE;
|
|
goto err1;
|
|
}
|
|
|
|
if (pnp_get_attr_string(node_inf, PNP_DRIVER_TYPE, &type, false) != B_OK) {
|
|
dprintf("Missing type in node registered by %s\n", driver_name);
|
|
|
|
free(driver_name);
|
|
res = B_BAD_VALUE;
|
|
goto err1;
|
|
}
|
|
|
|
TRACE(("driver: %s, type: %s\n", driver_name, type));
|
|
|
|
free(driver_name);
|
|
free(type);
|
|
}
|
|
|
|
// check whether this device already existed and thus is redetected
|
|
res = is_device_redetected(node_inf, parent, &redetected);
|
|
if (res != B_OK)
|
|
goto err1;
|
|
|
|
if (redetected) {
|
|
// upon redetect, resources are released instead of transferred and
|
|
// no node is returned
|
|
*node = NULL;
|
|
res = B_OK;
|
|
goto err1;
|
|
}
|
|
|
|
// transfer resources to device, unregistering all colliding devices;
|
|
// this cannot fail - we've already allocated the resource handle array
|
|
pnp_assign_io_resources(node_inf, io_resources);
|
|
|
|
pnp_create_node_links(node_inf, parent);
|
|
|
|
// make it public
|
|
res = mark_node_registered(node_inf);
|
|
if (res != B_OK)
|
|
goto err2;
|
|
|
|
// from now on, node won't get freed as ref_count has been increased by registration
|
|
|
|
// check whether searching for consumers should be deferred
|
|
if (pnp_postpone_probing(node_inf)) {
|
|
// return without decrementing ref_count - else node may get
|
|
// lost before deferred probe
|
|
*node = node_inf;
|
|
return B_OK;
|
|
}
|
|
|
|
res = pnp_initial_scan(node_inf);
|
|
if (res != B_OK)
|
|
goto err2;
|
|
|
|
pnp_load_driver_automatically(node_inf, false);
|
|
|
|
*node = node_inf;
|
|
|
|
TRACE(("done: node=%p\n", *node));
|
|
|
|
// alloc_node has set ref_count to one for safety, correct this now
|
|
pnp_remove_node_ref(node_inf);
|
|
return res;
|
|
|
|
err2:
|
|
// use this exit after i/o resources have been transferred to node
|
|
pnp_remove_node_ref(node_inf);
|
|
return res;
|
|
|
|
err1:
|
|
// alloc_node has set ref_count to one for safety, correct this now
|
|
pnp_remove_node_ref(node_inf);
|
|
err:
|
|
// always "consume" i/o resources
|
|
pnp_release_io_resources(io_resources);
|
|
return res;
|
|
}
|
|
|
|
|
|
// public: unregister device node
|
|
|
|
status_t
|
|
pnp_unregister_device(device_node_info *node)
|
|
{
|
|
device_node_info *dependency_list = NULL;
|
|
|
|
TRACE(("pnp_unregister_device(%p)\n", node));
|
|
|
|
if (node == NULL)
|
|
return B_OK;
|
|
|
|
// unregistered node and all children
|
|
benaphore_lock(&gNodeLock);
|
|
pnp_unregister_node_rec(node, &dependency_list);
|
|
benaphore_unlock(&gNodeLock);
|
|
|
|
pnp_unload_driver_automatically(node, false);
|
|
|
|
// tell drivers about their unregistration
|
|
pnp_notify_unregistration(dependency_list);
|
|
|
|
// now, we can safely decrease ref_count of unregistered nodes
|
|
pnp_unref_unregistered_nodes(dependency_list);
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
// remove <registered> flag of node and all children.
|
|
// list of all unregistered nodes is appended to <dependency_list>;
|
|
// (node_lock must be hold)
|
|
|
|
void
|
|
pnp_unregister_node_rec(device_node_info *node, device_node_info **dependency_list)
|
|
{
|
|
device_node_info *child, *next_child;
|
|
|
|
TRACE(("pnp_unregister_node_rec(%p)\n", node));
|
|
|
|
{
|
|
const char *driver_name, *type;
|
|
|
|
if (pnp_get_attr_string_nolock(node, PNP_DRIVER_DRIVER, &driver_name, false) != B_OK) {
|
|
dprintf("unregister_node: Missing driver filename in node\n");
|
|
goto err;
|
|
}
|
|
|
|
if (pnp_get_attr_string_nolock(node, PNP_DRIVER_TYPE, &type, false) != B_OK) {
|
|
dprintf("unregister_node: Missing type in node registered by %s\n", driver_name);
|
|
goto err;
|
|
}
|
|
|
|
TRACE(("driver: %s, type: %s\n", driver_name, type));
|
|
}
|
|
|
|
err:
|
|
// especially when we go through children, it can happen that they
|
|
// got unregistered already, so ignore them silently
|
|
if (!node->registered)
|
|
return;
|
|
|
|
TRACE(("Preparing unregistration\n"));
|
|
|
|
node->registered = false;
|
|
ADD_DL_LIST_HEAD(node, *dependency_list, notify_);
|
|
|
|
// unregister children recursively
|
|
for (child = node->children; child; child = next_child) {
|
|
next_child = child->siblings_next;
|
|
pnp_unregister_node_rec(child, dependency_list);
|
|
}
|
|
}
|
|
|
|
|
|
// defer probing of children.
|
|
// node_lock must be hold
|
|
|
|
void
|
|
pnp_defer_probing_of_children_nolock(device_node_info *node)
|
|
{
|
|
++node->defer_probing;
|
|
}
|
|
|
|
|
|
// defer probing of children
|
|
|
|
void
|
|
pnp_defer_probing_of_children(device_node_info *node)
|
|
{
|
|
benaphore_lock(&gNodeLock);
|
|
|
|
pnp_defer_probing_of_children_nolock(node);
|
|
|
|
benaphore_unlock(&gNodeLock);
|
|
}
|
|
|
|
|
|
// execute deferred probing of children
|
|
// (node_lock must be hold)
|
|
|
|
void
|
|
pnp_probe_waiting_children_nolock(device_node_info *node)
|
|
{
|
|
if (--node->defer_probing > 0 && node->unprobed_children != 0)
|
|
return;
|
|
|
|
TRACE(("execute deferred probing of parent %p\n", node));
|
|
|
|
while (node->unprobed_children) {
|
|
device_node_info *child = node->unprobed_children;
|
|
|
|
REMOVE_DL_LIST(child, node->unprobed_children, unprobed_);
|
|
|
|
// child may have been removed meanwhile
|
|
if (child->registered) {
|
|
benaphore_unlock(&gNodeLock);
|
|
|
|
if (pnp_initial_scan(child) == B_OK)
|
|
pnp_load_driver_automatically(node, false);
|
|
|
|
benaphore_lock(&gNodeLock);
|
|
}
|
|
|
|
// reference count was increment to keep node alive in wannabe list;
|
|
// this is not necessary anymore
|
|
pnp_remove_node_ref(node);
|
|
}
|
|
|
|
TRACE((".. done.\n"));
|
|
}
|
|
|
|
|
|
// execute deferred probing of children
|
|
|
|
void
|
|
pnp_probe_waiting_children(device_node_info *node)
|
|
{
|
|
benaphore_lock(&gNodeLock);
|
|
|
|
pnp_probe_waiting_children_nolock(node);
|
|
|
|
benaphore_unlock(&gNodeLock);
|
|
}
|
|
|