added a user friendly mode for listdev: it works for PCI git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@19262 a95241bf-73f2-0310-859d-f6bbb57e9c96
680 lines
15 KiB
C
680 lines
15 KiB
C
/*
|
|
* Copyright 2004-2005, Axel Dörfler, [email protected]. 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 handling.
|
|
|
|
To make sure that nodes persist as long as somebody is using them, we
|
|
maintain a reference count (ref_count) which is increased
|
|
- when the node is officially registered
|
|
- if someone loads the corresponding driver
|
|
- for each child
|
|
|
|
node_lock must be hold when updating of reference count, children
|
|
dependency list and during (un)-registration.
|
|
*/
|
|
|
|
|
|
#include "device_manager_private.h"
|
|
#include "dl_list.h"
|
|
|
|
#include <kernel.h>
|
|
|
|
#include <KernelExport.h>
|
|
#include <TypeConstants.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
//#define TRACE_NODES
|
|
#ifdef TRACE_NODES
|
|
# define TRACE(x) dprintf x
|
|
#else
|
|
# define TRACE(x) ;
|
|
#endif
|
|
|
|
|
|
benaphore gNodeLock;
|
|
|
|
uint32 sNodeID; // nodes counter
|
|
|
|
|
|
static void
|
|
put_level(int32 level)
|
|
{
|
|
while (level-- > 0)
|
|
dprintf(" ");
|
|
}
|
|
|
|
|
|
static void
|
|
dump_attribute(device_attr_info *attr, int32 level)
|
|
{
|
|
if (attr == NULL)
|
|
return;
|
|
|
|
put_level(level + 2);
|
|
dprintf("\"%s\" : ", attr->attr.name);
|
|
switch (attr->attr.type) {
|
|
case B_STRING_TYPE:
|
|
dprintf("string : \"%s\"", attr->attr.value.string);
|
|
break;
|
|
case B_UINT8_TYPE:
|
|
dprintf("uint8 : %u (%#x)", attr->attr.value.ui8, attr->attr.value.ui8);
|
|
break;
|
|
case B_UINT16_TYPE:
|
|
dprintf("uint16 : %u (%#x)", attr->attr.value.ui16, attr->attr.value.ui16);
|
|
break;
|
|
case B_UINT32_TYPE:
|
|
dprintf("uint32 : %lu (%#lx)", attr->attr.value.ui32, attr->attr.value.ui32);
|
|
break;
|
|
case B_UINT64_TYPE:
|
|
dprintf("uint64 : %Lu (%#Lx)", attr->attr.value.ui64, attr->attr.value.ui64);
|
|
break;
|
|
default:
|
|
dprintf("raw data");
|
|
}
|
|
dprintf("\n");
|
|
|
|
dump_attribute(attr->next, level);
|
|
}
|
|
|
|
|
|
/** free attributes of node
|
|
* if an attribute is still in use, it's deletion is postponed
|
|
*/
|
|
|
|
static void
|
|
free_node_attrs(device_node_info *node)
|
|
{
|
|
device_attr_info *attr, *next_attr;
|
|
|
|
for (attr = node->attributes; attr != NULL; attr = next_attr) {
|
|
next_attr = attr->next;
|
|
|
|
pnp_remove_attr_int(node, attr);
|
|
}
|
|
|
|
node->attributes = NULL;
|
|
}
|
|
|
|
|
|
// free node's I/O resource list, setting it to NULL
|
|
// (resources must have been released already)
|
|
|
|
static void
|
|
free_node_resources(device_node_info *node)
|
|
{
|
|
free(node->io_resources);
|
|
node->io_resources = NULL;
|
|
}
|
|
|
|
|
|
// free node
|
|
// (node lock must be hold)
|
|
|
|
static void
|
|
free_node(device_node_info *node)
|
|
{
|
|
const char *generator_name, *driver_name;
|
|
uint32 auto_id;
|
|
|
|
// ToDo: we lose memory here if the attributes are available!
|
|
if (pnp_get_attr_string_nolock(node, B_DRIVER_MODULE, &driver_name, false) != B_OK)
|
|
driver_name = "?";
|
|
|
|
TRACE(("free_node(node: %p, driver: %s)\n", node, driver_name));
|
|
|
|
// free associated auto ID, if requested
|
|
if (pnp_get_attr_string_nolock(node, PNP_MANAGER_ID_GENERATOR,
|
|
&generator_name, false) == B_OK) {
|
|
if (pnp_get_attr_uint32(node, PNP_MANAGER_AUTO_ID, &auto_id, false) != B_OK) {
|
|
TRACE(("Cannot find corresponding auto_id of generator %s", generator_name));
|
|
} else
|
|
dm_free_id(generator_name, auto_id);
|
|
}
|
|
|
|
dm_release_node_resources(node);
|
|
|
|
delete_sem(node->hook_sem);
|
|
delete_sem(node->load_block_sem);
|
|
|
|
free_node_attrs(node);
|
|
free_node_resources(node);
|
|
free(node);
|
|
}
|
|
|
|
|
|
// copy node attributes into node's attribute list
|
|
|
|
static status_t
|
|
copy_node_attrs(device_node_info *node, const device_attr *src)
|
|
{
|
|
status_t res;
|
|
const device_attr *src_attr;
|
|
device_attr_info *last_attr = NULL;
|
|
|
|
node->attributes = NULL;
|
|
|
|
// take care to keep attributes in same order;
|
|
// this is (currently) not necessary but a naive implementation would
|
|
// reverse order, which looks a bit odd (at least for the driver writer)
|
|
for (src_attr = src; src_attr->name != NULL; ++src_attr) {
|
|
device_attr_info *new_attr;
|
|
|
|
res = pnp_duplicate_node_attr( src_attr, &new_attr );
|
|
if (res != B_OK)
|
|
goto err;
|
|
|
|
//list_add_link_to_head(&node->attributes, new_attr);
|
|
ADD_DL_LIST_HEAD( new_attr, node->attributes, );
|
|
|
|
last_attr = new_attr;
|
|
}
|
|
|
|
return B_OK;
|
|
|
|
err:
|
|
free_node_attrs(node);
|
|
return res;
|
|
}
|
|
|
|
|
|
// allocate array of node I/O resources;
|
|
// all resource handles are set to zero, so if something goes wrong
|
|
// later on, resources aren't transferred to node
|
|
|
|
static status_t
|
|
allocate_node_resource_array(device_node_info *node, const io_resource_handle *resources)
|
|
{
|
|
const io_resource_handle *resource;
|
|
int num_resources = 0;
|
|
|
|
if (resources != NULL) {
|
|
for (resource = resources; *resource != NULL; ++resource)
|
|
++num_resources;
|
|
}
|
|
|
|
node->num_io_resources = num_resources;
|
|
|
|
node->io_resources = malloc((num_resources + 1) * sizeof(io_resource_handle));
|
|
if (node->io_resources == NULL)
|
|
return B_NO_MEMORY;
|
|
|
|
memset(node->io_resources, 0, (num_resources + 1) * sizeof(io_resource_handle));
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
// Device Manager private functions
|
|
|
|
|
|
/** allocate device node info structure;
|
|
* initially, ref_count is one to make sure node won't get destroyed by mistake
|
|
*/
|
|
|
|
status_t
|
|
dm_allocate_node(const device_attr *attrs, const io_resource_handle *resources,
|
|
device_node_info **_node)
|
|
{
|
|
device_node_info *node;
|
|
status_t res;
|
|
|
|
node = calloc(1, sizeof(*node));
|
|
if (node == NULL)
|
|
return B_NO_MEMORY;
|
|
|
|
res = copy_node_attrs(node, attrs);
|
|
if (res < 0)
|
|
goto err;
|
|
|
|
res = allocate_node_resource_array(node, resources);
|
|
if (res < 0)
|
|
goto err2;
|
|
|
|
res = node->hook_sem = create_sem(0, "pnp_hook");
|
|
if (res < 0)
|
|
goto err3;
|
|
|
|
res = node->load_block_sem = create_sem(0, "pnp_load_block");
|
|
if (res < 0)
|
|
goto err4;
|
|
|
|
list_init(&node->children);
|
|
|
|
node->parent = NULL;
|
|
node->rescan_depth = 0;
|
|
node->registered = false;
|
|
#if 0
|
|
node->verifying = false;
|
|
node->redetected = false;
|
|
node->init_finished = false;
|
|
#endif
|
|
node->ref_count = 1;
|
|
node->load_count = 0;
|
|
node->blocked_by_rescan = false;
|
|
node->automatically_loaded = false;
|
|
|
|
node->num_waiting_hooks = 0;
|
|
node->num_blocked_loads = 0;
|
|
node->load_block_count = 0;
|
|
node->loading = 0;
|
|
node->internal_id = sNodeID++;
|
|
|
|
TRACE(("dm_allocate_node(): new node %p\n", node));
|
|
|
|
*_node = node;
|
|
|
|
return B_OK;
|
|
|
|
err4:
|
|
delete_sem(node->hook_sem);
|
|
err3:
|
|
free_node_resources(node);
|
|
err2:
|
|
free_node_attrs(node);
|
|
err:
|
|
free(node);
|
|
return res;
|
|
}
|
|
|
|
|
|
void
|
|
dm_add_child_node(device_node_info *parent, device_node_info *node)
|
|
{
|
|
TRACE(("dm_add_child_node(parent = %p, child = %p)\n", parent, node));
|
|
|
|
if (parent == NULL)
|
|
return;
|
|
|
|
benaphore_lock(&gNodeLock);
|
|
|
|
// parent must not be destroyed as long as it has children
|
|
parent->ref_count++;
|
|
node->parent = parent;
|
|
|
|
// tell parent about us, so we get unregistered automatically if parent
|
|
// gets unregistered
|
|
list_add_item(&parent->children, node);
|
|
|
|
benaphore_unlock(&gNodeLock);
|
|
}
|
|
|
|
|
|
void
|
|
dm_get_node_nolock(device_node_info *node)
|
|
{
|
|
node->ref_count++;
|
|
}
|
|
|
|
|
|
// increase ref_count of node
|
|
|
|
void
|
|
dm_get_node(device_node_info *node)
|
|
{
|
|
TRACE(("dm_get_device_node(%p)\n", node));
|
|
|
|
if (node == NULL)
|
|
return;
|
|
|
|
benaphore_lock(&gNodeLock);
|
|
node->ref_count++;
|
|
benaphore_unlock(&gNodeLock);
|
|
}
|
|
|
|
|
|
// remove node reference and clean it up if necessary
|
|
// (gNodeLock must be hold)
|
|
|
|
void
|
|
dm_put_node_nolock(device_node_info *node)
|
|
{
|
|
do {
|
|
device_node_info *parent;
|
|
|
|
TRACE(("pnp_remove_node_ref_internal(ref_count of %p: %ld)\n", node, node->ref_count - 1));
|
|
|
|
// unregistered devices lose their I/O resources as soon as they
|
|
// are unloaded
|
|
if (!node->registered && node->loading == 0 && node->load_count == 0)
|
|
dm_release_node_resources(node);
|
|
|
|
if (--node->ref_count > 0)
|
|
return;
|
|
|
|
TRACE(("cleaning up %p (parent: %p)\n", node, node->parent));
|
|
|
|
// time to clean up
|
|
parent = node->parent;
|
|
|
|
if (parent != NULL)
|
|
list_remove_item(&parent->children, node);
|
|
|
|
free_node(node);
|
|
|
|
// unrolled recursive call: decrease ref_count of parent as well
|
|
node = parent;
|
|
} while (node != NULL);
|
|
}
|
|
|
|
|
|
#if 0
|
|
// public: find node with some node attributes given
|
|
|
|
device_node_info *
|
|
dm_find_device(device_node_info *parent, const device_attr *attrs)
|
|
{
|
|
device_node_info *node, *found_node;
|
|
|
|
found_node = NULL;
|
|
|
|
benaphore_lock(&gNodeLock);
|
|
|
|
for (node = gNodeList; node; node = node->next) {
|
|
const device_attr *attr;
|
|
|
|
// list contains removed devices too, so skip them
|
|
if (!node->registered)
|
|
continue;
|
|
|
|
if (parent != (device_node_info *)-1 && parent != node->parent)
|
|
continue;
|
|
|
|
for (attr = attrs; attr && attr->name; ++attr) {
|
|
device_attr_info *other = pnp_find_attr_nolock(node, attr->name, false, attr->type);
|
|
if (other == NULL || pnp_compare_attrs(attr, &other->attr))
|
|
break;
|
|
}
|
|
|
|
if (attr != NULL && attr->name != NULL)
|
|
continue;
|
|
|
|
// we found a node
|
|
if (found_node != NULL)
|
|
// but it wasn't the only one
|
|
return NULL;
|
|
|
|
// even though we found a node, keep on search to make sure no other
|
|
// node is matched too
|
|
found_node = node;
|
|
}
|
|
|
|
err:
|
|
benaphore_unlock(&gNodeLock);
|
|
|
|
return found_node;
|
|
}
|
|
#endif
|
|
|
|
|
|
void
|
|
dm_dump_node(device_node_info *node, int32 level)
|
|
{
|
|
device_node_info *child = NULL;
|
|
|
|
if (node == NULL)
|
|
return;
|
|
|
|
put_level(level);
|
|
dprintf("(%ld) @%p \"%s\"\n", level, node, node->driver ? node->driver->info.name : "---");
|
|
dump_attribute(node->attributes, level);
|
|
|
|
while ((child = (device_node_info *)list_get_next_item(&node->children, child)) != NULL) {
|
|
dm_dump_node(child, level + 1);
|
|
}
|
|
}
|
|
|
|
|
|
status_t
|
|
dm_init_nodes(void)
|
|
{
|
|
sNodeID = 1;
|
|
return benaphore_init(&gNodeLock, "device nodes");
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
// Functions part of the module API
|
|
|
|
|
|
/** remove node reference and clean it up if necessary */
|
|
|
|
void
|
|
dm_put_node(device_node_info *node)
|
|
{
|
|
TRACE(("dm_put_node(%p)\n", node));
|
|
|
|
benaphore_lock(&gNodeLock);
|
|
dm_put_node_nolock(node);
|
|
benaphore_unlock(&gNodeLock);
|
|
}
|
|
|
|
|
|
device_node_info *
|
|
dm_get_root(void)
|
|
{
|
|
dm_get_node(gRootNode);
|
|
return gRootNode;
|
|
}
|
|
|
|
|
|
device_node_info *
|
|
dm_get_parent(device_node_info *node)
|
|
{
|
|
dm_get_node(node->parent);
|
|
return node->parent;
|
|
}
|
|
|
|
|
|
status_t
|
|
dm_get_next_child_node(device_node_info *parent, device_node_info **_node,
|
|
const device_attr *attrs)
|
|
{
|
|
device_node_info *node = *_node;
|
|
device_node_info *nodeToPut = node;
|
|
|
|
benaphore_lock(&gNodeLock);
|
|
|
|
while ((node = (device_node_info *)list_get_next_item(&parent->children, node)) != NULL) {
|
|
const device_attr *attr;
|
|
|
|
// list contains removed devices too, so skip them
|
|
if (!node->registered)
|
|
continue;
|
|
|
|
for (attr = attrs; attr && attr->name; ++attr) {
|
|
device_attr_info *other = pnp_find_attr_nolock(node, attr->name, false, attr->type);
|
|
if (other == NULL || pnp_compare_attrs(attr, &other->attr))
|
|
break;
|
|
}
|
|
|
|
if (attr != NULL && attr->name != NULL)
|
|
continue;
|
|
|
|
// we found a node
|
|
dm_get_node_nolock(node);
|
|
*_node = node;
|
|
|
|
if (nodeToPut != NULL)
|
|
dm_put_node_nolock(nodeToPut);
|
|
|
|
benaphore_unlock(&gNodeLock);
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
if (nodeToPut != NULL)
|
|
dm_put_node_nolock(nodeToPut);
|
|
|
|
benaphore_unlock(&gNodeLock);
|
|
return B_ENTRY_NOT_FOUND;
|
|
}
|
|
|
|
|
|
// hold gNodeLock
|
|
static device_node_info *
|
|
device_manager_find_device(device_node_info *parent, uint32 id)
|
|
{
|
|
device_node_info *node = NULL;
|
|
|
|
if (parent->internal_id == id)
|
|
return parent;
|
|
|
|
while ((node = (device_node_info *)list_get_next_item(&parent->children, node)) != NULL) {
|
|
device_node_info *child = device_manager_find_device(node, id);
|
|
if (child != NULL)
|
|
return child;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
status_t
|
|
device_manager_control(const char *subsystem, uint32 function, void *buffer, size_t bufferSize)
|
|
{
|
|
switch (function) {
|
|
case DM_GET_ROOT: {
|
|
uint32 cookie;
|
|
if (!IS_USER_ADDRESS(buffer))
|
|
return B_BAD_ADDRESS;
|
|
if (bufferSize < sizeof(uint32))
|
|
return B_BAD_VALUE;
|
|
cookie = gRootNode->internal_id;
|
|
|
|
// copy back to user space
|
|
if (user_memcpy(buffer, &cookie, sizeof(uint32)) < B_OK)
|
|
return B_BAD_ADDRESS;
|
|
return B_OK;
|
|
}
|
|
case DM_GET_CHILD: {
|
|
uint32 cookie;
|
|
device_node_info *node;
|
|
device_node_info *child;
|
|
|
|
if (!IS_USER_ADDRESS(buffer))
|
|
return B_BAD_ADDRESS;
|
|
if (bufferSize < sizeof(uint32))
|
|
return B_BAD_VALUE;
|
|
if (user_memcpy(&cookie, buffer, sizeof(uint32)) < B_OK)
|
|
return B_BAD_ADDRESS;
|
|
|
|
benaphore_lock(&gNodeLock);
|
|
node = device_manager_find_device(gRootNode, cookie);
|
|
if (!node) {
|
|
benaphore_unlock(&gNodeLock);
|
|
return B_BAD_VALUE;
|
|
}
|
|
|
|
child = (device_node_info *)list_get_next_item(&node->children, NULL);
|
|
if (child)
|
|
cookie = child->internal_id;
|
|
benaphore_unlock(&gNodeLock);
|
|
|
|
if (!child)
|
|
return B_ENTRY_NOT_FOUND;
|
|
|
|
// copy back to user space
|
|
if (user_memcpy(buffer, &cookie, sizeof(uint32)) < B_OK)
|
|
return B_BAD_ADDRESS;
|
|
return B_OK;
|
|
}
|
|
case DM_GET_NEXT_CHILD: {
|
|
uint32 cookie;
|
|
device_node_info *node;
|
|
device_node_info *child;
|
|
|
|
if (!IS_USER_ADDRESS(buffer))
|
|
return B_BAD_ADDRESS;
|
|
if (bufferSize < sizeof(uint32))
|
|
return B_BAD_VALUE;
|
|
if (user_memcpy(&cookie, buffer, sizeof(uint32)) < B_OK)
|
|
return B_BAD_ADDRESS;
|
|
|
|
benaphore_lock(&gNodeLock);
|
|
node = device_manager_find_device(gRootNode, cookie);
|
|
if (!node) {
|
|
benaphore_unlock(&gNodeLock);
|
|
return B_BAD_VALUE;
|
|
}
|
|
child = (device_node_info *)list_get_next_item(&node->parent->children, node);
|
|
if (child)
|
|
cookie = child->internal_id;
|
|
benaphore_unlock(&gNodeLock);
|
|
|
|
if (!child)
|
|
return B_ENTRY_NOT_FOUND;
|
|
// copy back to user space
|
|
if (user_memcpy(buffer, &cookie, sizeof(uint32)) < B_OK)
|
|
return B_BAD_ADDRESS;
|
|
return B_OK;
|
|
}
|
|
case DM_GET_NEXT_ATTRIBUTE: {
|
|
struct dev_attr attr;
|
|
device_node_info *node;
|
|
uint32 i = 0;
|
|
device_attr_info *attr_info;
|
|
if (!IS_USER_ADDRESS(buffer))
|
|
return B_BAD_ADDRESS;
|
|
if (bufferSize < sizeof(struct dev_attr))
|
|
return B_BAD_VALUE;
|
|
if (user_memcpy(&attr, buffer, sizeof(struct dev_attr)) < B_OK)
|
|
return B_BAD_ADDRESS;
|
|
|
|
benaphore_lock(&gNodeLock);
|
|
node = device_manager_find_device(gRootNode, attr.node_cookie);
|
|
if (!node) {
|
|
benaphore_unlock(&gNodeLock);
|
|
return B_BAD_VALUE;
|
|
}
|
|
for (attr_info = node->attributes; attr.cookie > i && attr_info != NULL; attr_info = attr_info->next) {
|
|
i++;
|
|
}
|
|
|
|
if (!attr_info) {
|
|
benaphore_unlock(&gNodeLock);
|
|
return B_ENTRY_NOT_FOUND;
|
|
}
|
|
|
|
attr.cookie++;
|
|
|
|
strlcpy(attr.name, attr_info->attr.name, 254);
|
|
attr.type = attr_info->attr.type;
|
|
switch (attr_info->attr.type) {
|
|
case B_UINT8_TYPE:
|
|
attr.value.ui8 = attr_info->attr.value.ui8; break;
|
|
case B_UINT16_TYPE:
|
|
attr.value.ui16 = attr_info->attr.value.ui16; break;
|
|
case B_UINT32_TYPE:
|
|
attr.value.ui32 = attr_info->attr.value.ui32; break;
|
|
case B_UINT64_TYPE:
|
|
attr.value.ui64 = attr_info->attr.value.ui64; break;
|
|
case B_STRING_TYPE:
|
|
strlcpy(attr.value.string, attr_info->attr.value.string, 254); break;
|
|
/*case B_RAW_TYPE:
|
|
if (attr.value.raw.length > attr_info->attr.value.raw.length)
|
|
attr.value.raw.length = attr_info->attr.value.raw.length;
|
|
user_memcpy(attr.value.raw.data, attr_info->attr.value.raw.data,
|
|
attr.value.raw.length);
|
|
break;*/
|
|
}
|
|
|
|
benaphore_unlock(&gNodeLock);
|
|
|
|
// copy back to user space
|
|
if (user_memcpy(buffer, &attr, sizeof(struct dev_attr)) < B_OK)
|
|
return B_BAD_ADDRESS;
|
|
return B_OK;
|
|
}
|
|
};
|
|
return B_BAD_HANDLER;
|
|
}
|