⛏️ index : haiku.git

/*
 * Copyright 2008-2009, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */


#include <kdevice_manager.h>

#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <KernelExport.h>
#include <Locker.h>
#include <module.h>
#include <PCI.h>

#include <boot_device.h>
#include <device_manager_defs.h>
#include <fs/devfs.h>
#include <fs/KPath.h>
#include <generic_syscall.h>
#include <kernel.h>
#include <kmodule.h>
#include <util/AutoLock.h>
#include <util/DoublyLinkedList.h>
#include <util/Stack.h>

#include "AbstractModuleDevice.h"
#include "devfs_private.h"
#include "id_generator.h"
#include "io_resources.h"
#include "IOSchedulerRoster.h"


//#define TRACE_DEVICE_MANAGER
#ifdef TRACE_DEVICE_MANAGER
#	define TRACE(a) dprintf a
#else
#	define TRACE(a) ;
#endif


#define DEVICE_MANAGER_ROOT_NAME "system/devices_root/driver_v1"
#define DEVICE_MANAGER_GENERIC_NAME "system/devices_generic/driver_v1"


struct device_attr_private : device_attr,
		DoublyLinkedListLinkImpl<device_attr_private> {
						device_attr_private();
						device_attr_private(const device_attr& attr);
						~device_attr_private();

			status_t	InitCheck();
			status_t	CopyFrom(const device_attr& attr);

	static	int			Compare(const device_attr* attrA,
							const device_attr *attrB);

private:
			void		_Unset();
};

typedef DoublyLinkedList<device_attr_private> AttributeList;

// I/O resource
typedef struct io_resource_info {
	struct io_resource_info *prev, *next;
	device_node*		owner;			// associated node; NULL for temporary allocation
	io_resource			resource;		// info about actual resource
} io_resource_info;


namespace {


class Device : public AbstractModuleDevice,
	public DoublyLinkedListLinkImpl<Device> {
public:
							Device(device_node* node, const char* moduleName);
	virtual					~Device();

			status_t		InitCheck() const;

			const char*		ModuleName() const { return fModuleName; }

	virtual	status_t		InitDevice();
	virtual	void			UninitDevice();

	virtual void			Removed();

	virtual	status_t		Control(void* cookie, int32 op, void* buffer, size_t length);

			void			SetRemovedFromParent(bool removed)
								{ fRemovedFromParent = removed; }

private:
	const char*				fModuleName;
	bool					fRemovedFromParent;
};


} // unnamed namespace


typedef DoublyLinkedList<Device> DeviceList;
typedef DoublyLinkedList<device_node> NodeList;

struct device_node : DoublyLinkedListLinkImpl<device_node> {
							device_node(const char* moduleName,
								const device_attr* attrs);
							~device_node();

			status_t		InitCheck() const;

			status_t		AcquireResources(const io_resource* resources);

			const char*		ModuleName() const { return fModuleName; }
			device_node*	Parent() const { return fParent; }
			AttributeList&	Attributes() { return fAttributes; }
			const AttributeList& Attributes() const { return fAttributes; }

			status_t		InitDriver();
			bool			UninitDriver();
			void			UninitUnusedDriver();

			// The following two are only valid, if the node's driver is
			// initialized
			driver_module_info* DriverModule() const { return fDriver; }
			void*			DriverData() const { return fDriverData; }

			void			AddChild(device_node *node);
			void			RemoveChild(device_node *node);
			const NodeList&	Children() const { return fChildren; }
			void			DeviceRemoved();

			status_t		Register(device_node* parent);
			status_t		Probe(const char* devicePath, uint32 updateCycle);
			status_t		Reprobe();
			status_t		Rescan();

			bool			IsRegistered() const { return fRegistered; }
			bool			IsInitialized() const { return fInitialized > 0; }
			bool			IsProbed() const { return fLastUpdateCycle != 0; }
			uint32			Flags() const { return fFlags; }

			void			Acquire();
			bool			Release();

			const DeviceList& Devices() const { return fDevices; }
			void			AddDevice(Device* device);
			void			RemoveDevice(Device* device);

			int				CompareTo(const device_attr* attributes) const;
			device_node*	FindChild(const device_attr* attributes) const;
			device_node*	FindChild(const char* moduleName) const;

			int32			Priority();

			void			Dump(int32 level = 0);

private:
			status_t		_RegisterFixed(uint32& registered);
			bool			_AlwaysRegisterDynamic();
			status_t		_AddPath(Stack<KPath*>& stack, const char* path,
								const char* subPath = NULL);
			status_t		_GetNextDriverPath(void*& cookie, KPath& _path);
			status_t		_GetNextDriver(void* list,
								driver_module_info*& driver);
			status_t		_FindBestDriver(const char* path,
								driver_module_info*& bestDriver,
								float& bestSupport,
								device_node* previous = NULL);
			status_t		_RegisterPath(const char* path);
			status_t		_RegisterDynamic(device_node* previous = NULL);
			status_t		_RemoveChildren();
			device_node*	_FindCurrentChild();
			status_t		_Probe();
			void			_ReleaseWaiting();

	device_node*			fParent;
	NodeList				fChildren;
	int32					fRefCount;
	int32					fInitialized;
	bool					fRegistered;
	uint32					fFlags;
	float					fSupportsParent;
	uint32					fLastUpdateCycle;

	const char*				fModuleName;

	driver_module_info*		fDriver;
	void*					fDriverData;

	DeviceList				fDevices;
	AttributeList			fAttributes;
	ResourceList			fResources;
};

// flags in addition to those specified by B_DEVICE_FLAGS
enum node_flags {
	NODE_FLAG_REGISTER_INITIALIZED	= 0x00010000,
	NODE_FLAG_DEVICE_REMOVED		= 0x00020000,
	NODE_FLAG_OBSOLETE_DRIVER		= 0x00040000,
	NODE_FLAG_WAITING_FOR_DRIVER	= 0x00080000,

	NODE_FLAG_PUBLIC_MASK			= 0x0000ffff
};


static device_node *sRootNode;
static recursive_lock sLock;
static const char* sGenericContextPath;


//	#pragma mark -


static device_attr_private*
find_attr(const device_node* node, const char* name, bool recursive,
	type_code type)
{
	do {
		AttributeList::ConstIterator iterator
			= node->Attributes().GetIterator();

		while (iterator.HasNext()) {
			device_attr_private* attr = iterator.Next();

			if (type != B_ANY_TYPE && attr->type != type)
				continue;

			if (!strcmp(attr->name, name))
				return attr;
		}

		node = node->Parent();
	} while (node != NULL && recursive);

	return NULL;
}


static void
put_level(int32 level)
{
	while (level-- > 0)
		kprintf("   ");
}


static void
dump_attribute(device_attr* attr, int32 level)
{
	if (attr == NULL)
		return;

	put_level(level + 2);
	kprintf("\"%s\" : ", attr->name);
	switch (attr->type) {
		case B_STRING_TYPE:
			kprintf("string : \"%s\"", attr->value.string);
			break;
		case B_INT8_TYPE:
		case B_UINT8_TYPE:
			kprintf("uint8 : %" B_PRIu8 " (%#" B_PRIx8 ")", attr->value.ui8,
				attr->value.ui8);
			break;
		case B_INT16_TYPE:
		case B_UINT16_TYPE:
			kprintf("uint16 : %" B_PRIu16 " (%#" B_PRIx16 ")", attr->value.ui16,
				attr->value.ui16);
			break;
		case B_INT32_TYPE:
		case B_UINT32_TYPE:
			kprintf("uint32 : %" B_PRIu32 " (%#" B_PRIx32 ")", attr->value.ui32,
				attr->value.ui32);
			break;
		case B_INT64_TYPE:
		case B_UINT64_TYPE:
			kprintf("uint64 : %" B_PRIu64 " (%#" B_PRIx64 ")", attr->value.ui64,
				attr->value.ui64);
			break;
		default:
			kprintf("raw data");
	}
	kprintf("\n");
}


static int
dump_device_nodes(int argc, char** argv)
{
	sRootNode->Dump();
	return 0;
}


static void
publish_directories(const char* subPath)
{
	if (gBootDevice < 0) {
		if (subPath[0]) {
			// we only support the top-level directory for modules
			return;
		}

		// we can only iterate over the known modules to find all directories
		KPath path("drivers");
		if (path.Append(subPath) != B_OK)
			return;

		size_t length = strlen(path.Path()) + 1;
			// account for the separating '/'

		void* list = open_module_list_etc(path.Path(), "driver_v1");
		char name[B_FILE_NAME_LENGTH];
		size_t nameLength = sizeof(name);
		while (read_next_module_name(list, name, &nameLength) == B_OK) {
			if (nameLength == length)
				continue;

			char* leaf = name + length;
			char* end = strchr(leaf, '/');
			if (end != NULL)
				end[0] = '\0';

			path.SetTo(subPath);
			path.Append(leaf);

			devfs_publish_directory(path.Path());
		}
		close_module_list(list);
	} else {
		// TODO: implement module directory traversal!
	}
}


static status_t
control_device_manager(const char* subsystem, uint32 function, void* buffer,
	size_t bufferSize)
{
	// TODO: this function passes pointers to userland, and uses pointers
	// to device nodes that came from userland - this is completely unsafe
	// and should be changed.
	switch (function) {
		case DM_GET_ROOT:
		{
			device_node_cookie cookie;
			if (!IS_USER_ADDRESS(buffer))
				return B_BAD_ADDRESS;
			if (bufferSize != sizeof(device_node_cookie))
				return B_BAD_VALUE;
			cookie = (device_node_cookie)sRootNode;

			// copy back to user space
			return user_memcpy(buffer, &cookie, sizeof(device_node_cookie));
		}

		case DM_GET_CHILD:
		{
			if (!IS_USER_ADDRESS(buffer))
				return B_BAD_ADDRESS;
			if (bufferSize != sizeof(device_node_cookie))
				return B_BAD_VALUE;

			device_node_cookie cookie;
			if (user_memcpy(&cookie, buffer, sizeof(device_node_cookie)) < B_OK)
				return B_BAD_ADDRESS;

			device_node* node = (device_node*)cookie;
			NodeList::ConstIterator iterator = node->Children().GetIterator();

			if (!iterator.HasNext()) {
				return B_ENTRY_NOT_FOUND;
			}
			node = iterator.Next();
			cookie = (device_node_cookie)node;

			// copy back to user space
			return user_memcpy(buffer, &cookie, sizeof(device_node_cookie));
		}

		case DM_GET_NEXT_CHILD:
		{
			if (!IS_USER_ADDRESS(buffer))
				return B_BAD_ADDRESS;
			if (bufferSize != sizeof(device_node_cookie))
				return B_BAD_VALUE;

			device_node_cookie cookie;
			if (user_memcpy(&cookie, buffer, sizeof(device_node_cookie)) < B_OK)
				return B_BAD_ADDRESS;

			device_node* last = (device_node*)cookie;
			if (!last->Parent())
				return B_ENTRY_NOT_FOUND;

			NodeList::ConstIterator iterator
				= last->Parent()->Children().GetIterator();

			// skip those we already traversed
			while (iterator.HasNext()) {
				device_node* node = iterator.Next();

				if (node == last)
					break;
			}

			if (!iterator.HasNext())
				return B_ENTRY_NOT_FOUND;
			device_node* node = iterator.Next();
			cookie = (device_node_cookie)node;

			// copy back to user space
			return user_memcpy(buffer, &cookie, sizeof(device_node_cookie));
		}

		case DM_GET_NEXT_ATTRIBUTE:
		{
			struct device_attr_info attrInfo;
			if (!IS_USER_ADDRESS(buffer))
				return B_BAD_ADDRESS;
			if (bufferSize != sizeof(device_attr_info))
				return B_BAD_VALUE;
			if (user_memcpy(&attrInfo, buffer, sizeof(device_attr_info)) < B_OK)
				return B_BAD_ADDRESS;

			device_node* node = (device_node*)attrInfo.node_cookie;
			device_attr* last = (device_attr*)attrInfo.cookie;
			AttributeList::Iterator iterator = node->Attributes().GetIterator();
			// skip those we already traversed
			while (iterator.HasNext() && last != NULL) {
				device_attr* attr = iterator.Next();

				if (attr == last)
					break;
			}

			if (!iterator.HasNext()) {
				attrInfo.cookie = 0;
				return B_ENTRY_NOT_FOUND;
			}

			device_attr* attr = iterator.Next();
			attrInfo.cookie = (device_node_cookie)attr;
			if (attr->name != NULL)
				strlcpy(attrInfo.name, attr->name, 254);
			else
				attrInfo.name[0] = '\0';
			attrInfo.type = attr->type;
			switch (attrInfo.type) {
				case B_UINT8_TYPE:
					attrInfo.value.ui8 = attr->value.ui8;
					break;
				case B_UINT16_TYPE:
					attrInfo.value.ui16 = attr->value.ui16;
					break;
				case B_UINT32_TYPE:
					attrInfo.value.ui32 = attr->value.ui32;
					break;
				case B_UINT64_TYPE:
					attrInfo.value.ui64 = attr->value.ui64;
					break;
				case B_STRING_TYPE:
					if (attr->value.string != NULL)
						strlcpy(attrInfo.value.string, attr->value.string, 254);
					else
						attrInfo.value.string[0] = '\0';
					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;*/
			}

			// copy back to user space
			return user_memcpy(buffer, &attrInfo, sizeof(device_attr_info));
		}
	}

	return B_BAD_HANDLER;
}


//	#pragma mark - Device Manager module API


static status_t
rescan_node(device_node* node)
{
	RecursiveLocker _(sLock);
	return node->Rescan();
}


static status_t
register_node(device_node* parent, const char* moduleName,
	const device_attr* attrs, const io_resource* ioResources,
	device_node** _node)
{
	if ((parent == NULL && sRootNode != NULL) || moduleName == NULL)
		return B_BAD_VALUE;

	if (parent != NULL && parent->FindChild(attrs) != NULL) {
		// A node like this one already exists for this parent
		return B_NAME_IN_USE;
	}

	RecursiveLocker _(sLock);

	device_node* newNode = new(std::nothrow) device_node(moduleName, attrs);
	if (newNode == NULL)
		return B_NO_MEMORY;

	TRACE(("%p: register node \"%s\", parent %p\n", newNode, moduleName,
		parent));

	status_t status = newNode->InitCheck();
	if (status == B_OK)
		status = newNode->AcquireResources(ioResources);
	if (status == B_OK)
		status = newNode->Register(parent);

	if (status != B_OK) {
		newNode->Release();
		return status;
	}

	if (_node)
		*_node = newNode;

	return B_OK;
}


/*!	Unregisters the device \a node.

	If the node is currently in use, this function will return B_BUSY to
	indicate that the node hasn't been removed yet - it will still remove
	the node as soon as possible.
*/
static status_t
unregister_node(device_node* node)
{
	TRACE(("unregister_node(node %p)\n", node));
	RecursiveLocker _(sLock);

	bool initialized = node->IsInitialized();

	node->DeviceRemoved();

	return initialized ? B_BUSY : B_OK;
}


static status_t
get_driver(device_node* node, driver_module_info** _module, void** _data)
{
	if (node->DriverModule() == NULL)
		return B_NO_INIT;

	if (_module != NULL)
		*_module = node->DriverModule();
	if (_data != NULL)
		*_data = node->DriverData();

	return B_OK;
}


static device_node*
get_root_node(void)
{
	if (sRootNode != NULL)
		sRootNode->Acquire();

	return sRootNode;
}


static status_t
get_next_child_node(device_node* parent, const device_attr* attributes,
	device_node** _node)
{
	RecursiveLocker _(sLock);

	NodeList::ConstIterator iterator = parent->Children().GetIterator();
	device_node* last = *_node;

	// skip those we already traversed
	while (iterator.HasNext() && last != NULL) {
		device_node* node = iterator.Next();

		if (node != last)
			continue;
	}

	// find the next one that fits
	while (iterator.HasNext()) {
		device_node* node = iterator.Next();

		if (!node->IsRegistered())
			continue;

		if (!node->CompareTo(attributes)) {
			if (last != NULL)
				last->Release();

			node->Acquire();
			*_node = node;
			return B_OK;
		}
	}

	if (last != NULL)
		last->Release();

	return B_ENTRY_NOT_FOUND;
}


static device_node*
get_parent_node(device_node* node)
{
	if (node == NULL)
		return NULL;

	RecursiveLocker _(sLock);

	device_node* parent = node->Parent();
	parent->Acquire();

	return parent;
}


static void
put_node(device_node* node)
{
	RecursiveLocker _(sLock);
	node->Release();
}


static status_t
publish_device(device_node *node, const char *path, const char *moduleName)
{
	if (path == NULL || !path[0] || moduleName == NULL || !moduleName[0])
		return B_BAD_VALUE;

	RecursiveLocker _(sLock);
	dprintf("publish device: node %p, path %s, module %s\n", node, path,
		moduleName);

	Device* device = new(std::nothrow) Device(node, moduleName);
	if (device == NULL)
		return B_NO_MEMORY;

	status_t status = device->InitCheck();
	if (status == B_OK)
		status = devfs_publish_device(path, device);
	if (status != B_OK) {
		delete device;
		return status;
	}

	node->AddDevice(device);

	device_attr_private* attr;

	attr = new(std::nothrow) device_attr_private();
	if (attr != NULL) {
		char buf[256];
		sprintf(buf, "dev/%" B_PRIdINO "/path", device->ID());
		attr->name = strdup(buf);
		attr->type = B_STRING_TYPE;
		attr->value.string = strdup(path);
		node->Attributes().Add(attr);
	}

	attr = new(std::nothrow) device_attr_private();
	if (attr != NULL) {
		char buf[256];
		sprintf(buf, "dev/%" B_PRIdINO "/driver", device->ID());
		attr->name = strdup(buf);
		attr->type = B_STRING_TYPE;
		attr->value.string = strdup(moduleName);
		node->Attributes().Add(attr);
	}

	return B_OK;
}


static status_t
unpublish_device(device_node *node, const char *path)
{
	if (path == NULL)
		return B_BAD_VALUE;

	BaseDevice* baseDevice;
	status_t error = devfs_get_device(path, baseDevice);
	if (error != B_OK)
		return error;
	CObjectDeleter<BaseDevice, void, devfs_put_device>
		baseDevicePutter(baseDevice);

	Device* device = dynamic_cast<Device*>(baseDevice);
	if (device == NULL || device->Node() != node)
		return B_BAD_VALUE;

	return devfs_unpublish_device(device, true);
}


static status_t
get_attr_uint8(const device_node* node, const char* name, uint8* _value,
	bool recursive)
{
	if (node == NULL || name == NULL || _value == NULL)
		return B_BAD_VALUE;

	device_attr_private* attr = find_attr(node, name, recursive, B_UINT8_TYPE);
	if (attr == NULL)
		return B_NAME_NOT_FOUND;

	*_value = attr->value.ui8;
	return B_OK;
}


static status_t
get_attr_uint16(const device_node* node, const char* name, uint16* _value,
	bool recursive)
{
	if (node == NULL || name == NULL || _value == NULL)
		return B_BAD_VALUE;

	device_attr_private* attr = find_attr(node, name, recursive, B_UINT16_TYPE);
	if (attr == NULL)
		return B_NAME_NOT_FOUND;

	*_value = attr->value.ui16;
	return B_OK;
}


static status_t
get_attr_uint32(const device_node* node, const char* name, uint32* _value,
	bool recursive)
{
	if (node == NULL || name == NULL || _value == NULL)
		return B_BAD_VALUE;

	device_attr_private* attr = find_attr(node, name, recursive, B_UINT32_TYPE);
	if (attr == NULL)
		return B_NAME_NOT_FOUND;

	*_value = attr->value.ui32;
	return B_OK;
}


static status_t
get_attr_uint64(const device_node* node, const char* name,
	uint64* _value, bool recursive)
{
	if (node == NULL || name == NULL || _value == NULL)
		return B_BAD_VALUE;

	device_attr_private* attr = find_attr(node, name, recursive, B_UINT64_TYPE);
	if (attr == NULL)
		return B_NAME_NOT_FOUND;

	*_value = attr->value.ui64;
	return B_OK;
}


static status_t
get_attr_string(const device_node* node, const char* name,
	const char** _value, bool recursive)
{
	if (node == NULL || name == NULL || _value == NULL)
		return B_BAD_VALUE;

	device_attr_private* attr = find_attr(node, name, recursive, B_STRING_TYPE);
	if (attr == NULL)
		return B_NAME_NOT_FOUND;

	*_value = attr->value.string;
	return B_OK;
}


static status_t
get_attr_raw(const device_node* node, const char* name, const void** _data,
	size_t* _length, bool recursive)
{
	if (node == NULL || name == NULL || (_data == NULL && _length == NULL))
		return B_BAD_VALUE;

	device_attr_private* attr = find_attr(node, name, recursive, B_RAW_TYPE);
	if (attr == NULL)
		return B_NAME_NOT_FOUND;

	if (_data != NULL)
		*_data = attr->value.raw.data;
	if (_length != NULL)
		*_length = attr->value.raw.length;
	return B_OK;
}


static status_t
get_next_attr(device_node* node, device_attr** _attr)
{
	if (node == NULL)
		return B_BAD_VALUE;

	device_attr_private* next;
	device_attr_private* attr = *(device_attr_private**)_attr;

	if (attr != NULL) {
		// next attribute
		next = attr->GetDoublyLinkedListLink()->next;
	} else {
		// first attribute
		next = node->Attributes().First();
	}

	*_attr = next;

	return next ? B_OK : B_ENTRY_NOT_FOUND;
}


static status_t
find_child_node(device_node* parent, const device_attr* attributes,
	device_node** _node, bool *_lastFound)
{
	RecursiveLocker _(sLock);

	NodeList::ConstIterator iterator = parent->Children().GetIterator();
	device_node* last = *_node;

	// find the next one that fits
	while (iterator.HasNext()) {
		device_node* node = iterator.Next();

		if (!node->IsRegistered())
			continue;

		if (node == last)
			*_lastFound = true;
		else if (!node->CompareTo(attributes) && *_lastFound) {
			if (last != NULL)
				last->Release();

			node->Acquire();
			*_node = node;
			return B_OK;
		}
		if (find_child_node(node, attributes, _node, _lastFound) == B_OK)
			return B_OK;
	}

	return B_ENTRY_NOT_FOUND;
}


static status_t
find_child_node(device_node* parent, const device_attr* attributes,
	device_node** _node)
{
	device_node* last = *_node;
	bool lastFound = last == NULL;
	status_t status = find_child_node(parent, attributes, _node, &lastFound);
	if (status == B_ENTRY_NOT_FOUND && last != NULL && lastFound)
		last->Release();
	return status;
}


struct device_manager_info gDeviceManagerModule = {
	{
		B_DEVICE_MANAGER_MODULE_NAME,
		0,
		NULL
	},

	// device nodes
	rescan_node,
	register_node,
	unregister_node,
	get_driver,
	get_root_node,
	get_next_child_node,
	get_parent_node,
	put_node,

	// devices
	publish_device,
	unpublish_device,

	// I/O resources

	// ID generator
	dm_create_id,
	dm_free_id,

	// attributes
	get_attr_uint8,
	get_attr_uint16,
	get_attr_uint32,
	get_attr_uint64,
	get_attr_string,
	get_attr_raw,
	get_next_attr,
	find_child_node
};


//	#pragma mark - device_attr


device_attr_private::device_attr_private()
{
	name = NULL;
	type = 0;
	value.raw.data = NULL;
	value.raw.length = 0;
}


device_attr_private::device_attr_private(const device_attr& attr)
{
	CopyFrom(attr);
}


device_attr_private::~device_attr_private()
{
	_Unset();
}


status_t
device_attr_private::InitCheck()
{
	return name != NULL ? B_OK : B_NO_INIT;
}


status_t
device_attr_private::CopyFrom(const device_attr& attr)
{
	name = strdup(attr.name);
	if (name == NULL)
		return B_NO_MEMORY;

	type = attr.type;

	switch (type) {
		case B_UINT8_TYPE:
		case B_UINT16_TYPE:
		case B_UINT32_TYPE:
		case B_UINT64_TYPE:
			value.ui64 = attr.value.ui64;
			break;

		case B_STRING_TYPE:
			if (attr.value.string != NULL) {
				value.string = strdup(attr.value.string);
				if (value.string == NULL) {
					_Unset();
					return B_NO_MEMORY;
				}
			} else
				value.string = NULL;
			break;

		case B_RAW_TYPE:
			value.raw.data = malloc(attr.value.raw.length);
			if (value.raw.data == NULL) {
				_Unset();
				return B_NO_MEMORY;
			}

			value.raw.length = attr.value.raw.length;
			memcpy((void*)value.raw.data, attr.value.raw.data,
				attr.value.raw.length);
			break;

		default:
			return B_BAD_VALUE;
	}

	return B_OK;
}


void
device_attr_private::_Unset()
{
	if (type == B_STRING_TYPE)
		free((char*)value.string);
	else if (type == B_RAW_TYPE)
		free((void*)value.raw.data);

	free((char*)name);

	name = NULL;
	value.raw.data = NULL;
	value.raw.length = 0;
}


/*static*/ int
device_attr_private::Compare(const device_attr* attrA, const device_attr *attrB)
{
	if (attrA->type != attrB->type)
		return -1;

	switch (attrA->type) {
		case B_UINT8_TYPE:
			return (int)attrA->value.ui8 - (int)attrB->value.ui8;

		case B_UINT16_TYPE:
			return (int)attrA->value.ui16 - (int)attrB->value.ui16;

		case B_UINT32_TYPE:
			if (attrA->value.ui32 > attrB->value.ui32)
				return 1;
			if (attrA->value.ui32 < attrB->value.ui32)
				return -1;
			return 0;

		case B_UINT64_TYPE:
			if (attrA->value.ui64 > attrB->value.ui64)
				return 1;
			if (attrA->value.ui64 < attrB->value.ui64)
				return -1;
			return 0;

		case B_STRING_TYPE:
			return strcmp(attrA->value.string, attrB->value.string);

		case B_RAW_TYPE:
			if (attrA->value.raw.length != attrB->value.raw.length)
				return -1;

			return memcmp(attrA->value.raw.data, attrB->value.raw.data,
				attrA->value.raw.length);
	}

	return -1;
}


//	#pragma mark - Device


Device::Device(device_node* node, const char* moduleName)
	:
	fModuleName(strdup(moduleName)),
	fRemovedFromParent(false)
{
	fNode = node;
}


Device::~Device()
{
	free((char*)fModuleName);
}


status_t
Device::InitCheck() const
{
	return fModuleName != NULL ? B_OK : B_NO_MEMORY;
}


status_t
Device::InitDevice()
{
	RecursiveLocker _(sLock);

	if ((fNode->Flags() & NODE_FLAG_DEVICE_REMOVED) != 0) {
		// TODO: maybe the device should be unlinked in devfs, too
		return ENODEV;
	}
	if ((fNode->Flags() & NODE_FLAG_WAITING_FOR_DRIVER) != 0)
		return B_BUSY;

	if (fInitialized++ > 0) {
		fNode->InitDriver();
			// acquire another reference to our parent as well
		return B_OK;
	}

	status_t status = get_module(ModuleName(), (module_info**)&fDeviceModule);
	if (status == B_OK) {
		// our parent always has to be initialized
		status = fNode->InitDriver();
	}
	if (status < B_OK) {
		fInitialized--;
		return status;
	}

	if (Module()->init_device != NULL)
		status = Module()->init_device(fNode->DriverData(), &fDeviceData);

	if (status < B_OK) {
		fNode->UninitDriver();
		fInitialized--;

		put_module(ModuleName());
		fDeviceModule = NULL;
		fDeviceData = NULL;
	}

	return status;
}


void
Device::UninitDevice()
{
	RecursiveLocker _(sLock);

	if (fInitialized-- > 1) {
		fNode->UninitDriver();
		return;
	}

	TRACE(("uninit driver for node %p\n", this));

	if (Module()->uninit_device != NULL)
		Module()->uninit_device(fDeviceData);

	fDeviceModule = NULL;
	fDeviceData = NULL;

	put_module(ModuleName());

	fNode->UninitDriver();
}


void
Device::Removed()
{
	RecursiveLocker _(sLock);

	if (!fRemovedFromParent)
		fNode->RemoveDevice(this);

	delete this;
}


status_t
Device::Control(void* _cookie, int32 op, void* buffer, size_t length)
{
	switch (op) {
		case B_GET_DRIVER_FOR_DEVICE:
		{
			char* path = NULL;
			status_t status = module_get_path(ModuleName(), &path);
			if (status != B_OK)
				return status;
			if (length != 0 && length <= strlen(path))
				return ERANGE;
			status = user_strlcpy(static_cast<char*>(buffer), path, length);
			free(path);
			return status;
		}
		default:
			return AbstractModuleDevice::Control(_cookie, op, buffer, length);;
	}
}


//	#pragma mark - device_node


device_node::device_node(const char* moduleName, const device_attr* attrs)
{
	fModuleName = strdup(moduleName);
	if (fModuleName == NULL)
		return;

	fParent = NULL;
	fRefCount = 1;
	fInitialized = 0;
	fRegistered = false;
	fFlags = 0;
	fSupportsParent = 0.0;
	fLastUpdateCycle = 0;
	fDriver = NULL;
	fDriverData = NULL;

	// copy attributes

	while (attrs != NULL && attrs->name != NULL) {
		device_attr_private* attr
			= new(std::nothrow) device_attr_private(*attrs);
		if (attr == NULL)
			break;

		fAttributes.Add(attr);
		attrs++;
	}

	device_attr_private* attr = new(std::nothrow) device_attr_private();
	if (attr != NULL) {
		attr->name = strdup("device/driver");
		attr->type = B_STRING_TYPE;
		attr->value.string = strdup(fModuleName);
		fAttributes.Add(attr);
	}

	get_attr_uint32(this, B_DEVICE_FLAGS, &fFlags, false);
	fFlags &= NODE_FLAG_PUBLIC_MASK;
}


device_node::~device_node()
{
	TRACE(("delete node %p\n", this));
	ASSERT(DriverModule() == NULL);

	if (Parent() != NULL) {
		if ((fFlags & NODE_FLAG_OBSOLETE_DRIVER) != 0) {
			// This driver has been obsoleted; another driver has been waiting
			// for us - make it available
			Parent()->_ReleaseWaiting();
		}
		Parent()->RemoveChild(this);
	}

	// Delete children
	while (device_node* child = fChildren.RemoveHead()) {
		delete child;
	}

	// Delete devices
	while (Device* device = fDevices.RemoveHead()) {
		device->SetRemovedFromParent(true);
		devfs_unpublish_device(device, true);
	}

	// Delete attributes
	while (device_attr_private* attr = fAttributes.RemoveHead()) {
		delete attr;
	}

	// Delete resources
	while (io_resource_private* resource = fResources.RemoveHead()) {
		delete resource;
	}

	free((char*)fModuleName);
}


status_t
device_node::InitCheck() const
{
	return fModuleName != NULL ? B_OK : B_NO_MEMORY;
}


status_t
device_node::AcquireResources(const io_resource* resources)
{
	if (resources == NULL)
		return B_OK;

	for (uint32 i = 0; resources[i].type != 0; i++) {
		io_resource_private* resource = new(std::nothrow) io_resource_private;
		if (resource == NULL)
			return B_NO_MEMORY;

		status_t status = resource->Acquire(resources[i]);
		if (status != B_OK) {
			delete resource;
			return status;
		}

		fResources.Add(resource);
	}

	return B_OK;
}


status_t
device_node::InitDriver()
{
	if (fInitialized++ > 0) {
		if (Parent() != NULL) {
			Parent()->InitDriver();
				// acquire another reference to our parent as well
		}
		Acquire();
		return B_OK;
	}

	status_t status = get_module(ModuleName(), (module_info**)&fDriver);
	if (status == B_OK && Parent() != NULL) {
		// our parent always has to be initialized
		status = Parent()->InitDriver();
	}
	if (status < B_OK) {
		fInitialized--;
		return status;
	}

	if (fDriver->init_driver != NULL) {
		status = fDriver->init_driver(this, &fDriverData);
		if (status != B_OK) {
			dprintf("driver %s init failed: %s\n", ModuleName(),
				strerror(status));
		}
	}

	if (status < B_OK) {
		if (Parent() != NULL)
			Parent()->UninitDriver();
		fInitialized--;

		put_module(ModuleName());
		fDriver = NULL;
		fDriverData = NULL;
		return status;
	}

	Acquire();
	return B_OK;
}


bool
device_node::UninitDriver()
{
	if (fInitialized-- > 1) {
		if (Parent() != NULL)
			Parent()->UninitDriver();
		Release();
		return false;
	}

	TRACE(("uninit driver for node %p\n", this));

	if (fDriver->uninit_driver != NULL)
		fDriver->uninit_driver(fDriverData);

	fDriver = NULL;
	fDriverData = NULL;

	put_module(ModuleName());

	if (Parent() != NULL)
		Parent()->UninitDriver();
	Release();

	return true;
}


void
device_node::AddChild(device_node* node)
{
	// we must not be destroyed	as long as we have children
	Acquire();
	node->fParent = this;

	int32 priority = node->Priority();

	// Enforce an order in which the children are traversed - from most
	// specific to least specific child.
	NodeList::Iterator iterator = fChildren.GetIterator();
	device_node* before = NULL;
	while (iterator.HasNext()) {
		device_node* child = iterator.Next();
		if (child->Priority() < priority) {
			before = child;
			break;
		}
	}

	fChildren.InsertBefore(before, node);
}


void
device_node::RemoveChild(device_node* node)
{
	node->fParent = NULL;
	fChildren.Remove(node);
	Release();
}


/*!	Registers this node, and all of its children that have to be registered.
	Also initializes the driver and keeps it that way on return in case
	it returns successfully.
*/
status_t
device_node::Register(device_node* parent)
{
	// make it public
	if (parent != NULL)
		parent->AddChild(this);
	else
		sRootNode = this;

	status_t status = InitDriver();
	if (status != B_OK)
		return status;

	if ((fFlags & B_KEEP_DRIVER_LOADED) != 0) {
		// We keep this driver loaded by having it always initialized
		InitDriver();
	}

	fFlags |= NODE_FLAG_REGISTER_INITIALIZED;
		// We don't uninitialize the driver - this is done by the caller
		// in order to save reinitializing during driver loading.

	uint32 registeredFixedCount;
	status = _RegisterFixed(registeredFixedCount);
	if (status != B_OK) {
		UninitUnusedDriver();
		return status;
	}

	// Register the children the driver wants

	if (DriverModule()->register_child_devices != NULL) {
		status = DriverModule()->register_child_devices(DriverData());
		if (status != B_OK) {
			UninitUnusedDriver();
			return status;
		}

		if (!fChildren.IsEmpty()) {
			fRegistered = true;
			return B_OK;
		}
	}

	if (registeredFixedCount > 0) {
		// Nodes with fixed children cannot have any dynamic children, so bail
		// out here
		fRegistered = true;
		return B_OK;
	}

	// Register all possible child device nodes

	status = _RegisterDynamic();
	if (status == B_OK)
		fRegistered = true;
	else
		UninitUnusedDriver();

	return status;
}


/*!	Registers any children that are identified via the B_DEVICE_FIXED_CHILD
	attribute.
	If any of these children cannot be registered, this call will fail (we
	don't remove children we already registered up to this point in this case).
*/
status_t
device_node::_RegisterFixed(uint32& registered)
{
	AttributeList::Iterator iterator = fAttributes.GetIterator();
	registered = 0;

	while (iterator.HasNext()) {
		device_attr_private* attr = iterator.Next();
		if (strcmp(attr->name, B_DEVICE_FIXED_CHILD))
			continue;

		driver_module_info* driver;
		status_t status = get_module(attr->value.string,
			(module_info**)&driver);
		if (status != B_OK) {
			TRACE(("register fixed child %s failed: %s\n", attr->value.string,
				strerror(status)));
			return status;
		}

		if (driver->register_device != NULL) {
			status = driver->register_device(this);
			if (status == B_OK)
				registered++;
		}

		put_module(attr->value.string);

		if (status != B_OK)
			return status;
	}

	return B_OK;
}


status_t
device_node::_AddPath(Stack<KPath*>& stack, const char* basePath,
	const char* subPath)
{
	KPath* path = new(std::nothrow) KPath;
	if (path == NULL)
		return B_NO_MEMORY;

	status_t status = path->SetTo(basePath);
	if (status == B_OK && subPath != NULL && subPath[0])
		status = path->Append(subPath);
	if (status == B_OK)
		status = stack.Push(path);

	TRACE(("  add path: \"%s\", %" B_PRId32 "\n", path->Path(), status));

	if (status != B_OK)
		delete path;

	return status;
}


status_t
device_node::_GetNextDriverPath(void*& cookie, KPath& _path)
{
	Stack<KPath*>* stack = NULL;

	if (cookie == NULL) {
		// find all paths and add them
		stack = new(std::nothrow) Stack<KPath*>();
		if (stack == NULL)
			return B_NO_MEMORY;

		StackDeleter<KPath*> stackDeleter(stack);

		bool generic = false;
		uint16 type = 0;
		uint16 subType = 0;
		if (get_attr_uint16(this, B_DEVICE_TYPE, &type, false) != B_OK
			|| get_attr_uint16(this, B_DEVICE_SUB_TYPE, &subType, false)
					!= B_OK)
			generic = true;

		// TODO: maybe make this extendible via settings file?
		switch (type) {
			case PCI_mass_storage:
				switch (subType) {
					case PCI_scsi:
						_AddPath(*stack, "busses", "scsi");
						_AddPath(*stack, "busses", "virtio");
						break;
					case PCI_ide:
						_AddPath(*stack, "busses", "ata");
						_AddPath(*stack, "busses", "ide");
						break;
					case PCI_sata:
						// TODO: check for ahci interface
						_AddPath(*stack, "busses", "scsi");
						_AddPath(*stack, "busses", "ata");
						_AddPath(*stack, "busses", "ide");
						break;
					case PCI_nvm:
						_AddPath(*stack, "drivers", "disk");
						break;
					default:
						_AddPath(*stack, "busses");
						break;
				}
				break;
			case PCI_serial_bus:
				switch (subType) {
					case PCI_firewire:
						_AddPath(*stack, "busses", "firewire");
						break;
					case PCI_usb:
						_AddPath(*stack, "busses", "usb");
						break;
					default:
						_AddPath(*stack, "busses");
						break;
				}
				break;
			case PCI_network:
				_AddPath(*stack, "drivers", "net");
				_AddPath(*stack, "busses", "virtio");
				break;
			case PCI_display:
				_AddPath(*stack, "drivers", "graphics");
				_AddPath(*stack, "busses", "virtio");
				break;
			case PCI_multimedia:
				switch (subType) {
					case PCI_audio:
					case PCI_hd_audio:
						_AddPath(*stack, "drivers", "audio");
						_AddPath(*stack, "busses", "virtio");
						break;
					case PCI_video:
						_AddPath(*stack, "drivers", "video");
						break;
					default:
						_AddPath(*stack, "drivers");
						break;
				}
				break;
			case PCI_base_peripheral:
				switch (subType) {
					case PCI_sd_host:
						_AddPath(*stack, "busses", "mmc");
						break;
					case PCI_system_peripheral_other:
						_AddPath(*stack, "busses", "mmc");
						_AddPath(*stack, "drivers");
						break;
					default:
						_AddPath(*stack, "drivers");
						break;
				}
				break;
			case PCI_encryption_decryption:
				switch (subType) {
					case PCI_encryption_decryption_other:
						_AddPath(*stack, "busses", "random");
						break;
					default:
						_AddPath(*stack, "drivers");
						break;
				}
				break;
			case PCI_data_acquisition:
				switch (subType) {
					case PCI_data_acquisition_other:
						_AddPath(*stack, "busses", "i2c");
						_AddPath(*stack, "drivers");
						break;
					default:
						_AddPath(*stack, "drivers");
						break;
				}
				break;
			default:
				if (sRootNode == this) {
					_AddPath(*stack, "busses/pci");
					_AddPath(*stack, "bus_managers");
				} else if (!generic) {
					_AddPath(*stack, "drivers");
					_AddPath(*stack, "busses/virtio");
				} else {
					// For generic drivers, we only allow busses when the
					// request is more specified
					if (sGenericContextPath != NULL
						&& (!strcmp(sGenericContextPath, "disk")
							|| !strcmp(sGenericContextPath, "ports")
							|| !strcmp(sGenericContextPath, "bus"))) {
						_AddPath(*stack, "busses");
					}
					const char* bus;
					if (get_attr_string(this, B_DEVICE_BUS, &bus, false) == B_OK) {
						if (strcmp(bus, "virtio") == 0)
							_AddPath(*stack, "busses/scsi");
					}
					_AddPath(*stack, "drivers", sGenericContextPath);
					_AddPath(*stack, "busses/i2c");
					_AddPath(*stack, "busses/random");
					_AddPath(*stack, "busses/virtio");
					_AddPath(*stack, "bus_managers/pci");
					_AddPath(*stack, "busses/pci");
					_AddPath(*stack, "busses/mmc");
				}
				break;
		}

		stackDeleter.Detach();

		cookie = (void*)stack;
	} else
		stack = static_cast<Stack<KPath*>*>(cookie);

	KPath* path;
	if (stack->Pop(&path)) {
		_path.Adopt(*path);
		delete path;
		return B_OK;
	}

	delete stack;
	return B_ENTRY_NOT_FOUND;
}


status_t
device_node::_GetNextDriver(void* list, driver_module_info*& driver)
{
	while (true) {
		char name[B_FILE_NAME_LENGTH];
		size_t nameLength = sizeof(name);

		status_t status = read_next_module_name(list, name, &nameLength);
		if (status != B_OK)
			return status;

		if (!strcmp(fModuleName, name))
			continue;

		if (get_module(name, (module_info**)&driver) != B_OK)
			continue;

		if (driver->supports_device == NULL
			|| driver->register_device == NULL) {
			put_module(name);
			continue;
		}

		return B_OK;
	}
}


status_t
device_node::_FindBestDriver(const char* path, driver_module_info*& bestDriver,
	float& bestSupport, device_node* previous)
{
	if (bestDriver == NULL)
		bestSupport = previous != NULL ? previous->fSupportsParent : 0.0f;

	void* list = open_module_list_etc(path, "driver_v1");
	driver_module_info* driver;
	while (_GetNextDriver(list, driver) == B_OK) {
		if (previous != NULL && driver == previous->DriverModule()) {
			put_module(driver->info.name);
			continue;
		}

		float support = driver->supports_device(this);
		if (support > bestSupport) {
			if (bestDriver != NULL)
				put_module(bestDriver->info.name);

			bestDriver = driver;
			bestSupport = support;
			continue;
				// keep reference to best module around
		}

		put_module(driver->info.name);
	}
	close_module_list(list);

	return bestDriver != NULL ? B_OK : B_ENTRY_NOT_FOUND;
}


status_t
device_node::_RegisterPath(const char* path)
{
	void* list = open_module_list_etc(path, "driver_v1");
	driver_module_info* driver;
	uint32 count = 0;

	while (_GetNextDriver(list, driver) == B_OK) {
		float support = driver->supports_device(this);
		if (support > 0.0) {
			TRACE(("  register module \"%s\", support %f\n", driver->info.name,
				support));
			if (driver->register_device(this) == B_OK)
				count++;
		}

		put_module(driver->info.name);
	}
	close_module_list(list);

	return count > 0 ? B_OK : B_ENTRY_NOT_FOUND;
}


bool
device_node::_AlwaysRegisterDynamic()
{
	uint16 type = 0;
	uint16 subType = 0;
	get_attr_uint16(this, B_DEVICE_TYPE, &type, false);
	get_attr_uint16(this, B_DEVICE_SUB_TYPE, &subType, false);

	switch (type) {
		case PCI_serial_bus:
		case PCI_bridge:
		case PCI_encryption_decryption:
		case 0:
			return true;
	}
	return false;
		// TODO: we may want to be a bit more specific in the future
}


status_t
device_node::_RegisterDynamic(device_node* previous)
{
	// If this is not a bus, we don't have to scan it
	if (find_attr(this, B_DEVICE_BUS, false, B_STRING_TYPE) == NULL)
		return B_OK;

	// If we're not being probed, we honour the B_FIND_CHILD_ON_DEMAND
	// requirements
	if (!IsProbed() && (fFlags & B_FIND_CHILD_ON_DEMAND) != 0
		&& !_AlwaysRegisterDynamic())
		return B_OK;

	KPath path;

	if ((fFlags & B_FIND_MULTIPLE_CHILDREN) == 0) {
		// find the one driver
		driver_module_info* bestDriver = NULL;
		float bestSupport = 0.0;
		void* cookie = NULL;

		while (_GetNextDriverPath(cookie, path) == B_OK) {
			_FindBestDriver(path.Path(), bestDriver, bestSupport, previous);
		}

		if (bestDriver != NULL) {
			TRACE(("  register best module \"%s\", support %f\n",
				bestDriver->info.name, bestSupport));
			if (bestDriver->register_device(this) == B_OK) {
				// There can only be one node of this driver
				// (usually only one at all, but there might be a new driver
				// "waiting" for its turn)
				device_node* child = FindChild(bestDriver->info.name);
				if (child != NULL) {
					child->fSupportsParent = bestSupport;
					if (previous != NULL) {
						previous->fFlags |= NODE_FLAG_OBSOLETE_DRIVER;
						previous->Release();
						child->fFlags |= NODE_FLAG_WAITING_FOR_DRIVER;
					}
				}
				// TODO: if this fails, we could try the second best driver,
				// and so on...
			}
			put_module(bestDriver->info.name);
		}
	} else {
		// register all drivers that match
		void* cookie = NULL;
		while (_GetNextDriverPath(cookie, path) == B_OK) {
			_RegisterPath(path.Path());
		}
	}

	return B_OK;
}


void
device_node::_ReleaseWaiting()
{
	NodeList::Iterator iterator = fChildren.GetIterator();
	while (iterator.HasNext()) {
		device_node* child = iterator.Next();

		child->fFlags &= ~NODE_FLAG_WAITING_FOR_DRIVER;
	}
}


status_t
device_node::_RemoveChildren()
{
	NodeList::Iterator iterator = fChildren.GetIterator();
	while (iterator.HasNext()) {
		device_node* child = iterator.Next();
		child->Release();
	}

	return fChildren.IsEmpty() ? B_OK : B_BUSY;
}


device_node*
device_node::_FindCurrentChild()
{
	NodeList::Iterator iterator = fChildren.GetIterator();
	while (iterator.HasNext()) {
		device_node* child = iterator.Next();

		if ((child->Flags() & NODE_FLAG_WAITING_FOR_DRIVER) == 0)
			return child;
	}

	return NULL;
}


status_t
device_node::_Probe()
{
	device_node* previous = NULL;

	if (IsProbed() && !fChildren.IsEmpty()
		&& (fFlags & (B_FIND_CHILD_ON_DEMAND | B_FIND_MULTIPLE_CHILDREN))
				== B_FIND_CHILD_ON_DEMAND) {
		// We already have a driver that claims this node; remove all
		// (unused) nodes, and evaluate it again
		_RemoveChildren();

		previous = _FindCurrentChild();
		if (previous != NULL) {
			// This driver is still active - give it back the reference
			// that was stolen by _RemoveChildren() - _RegisterDynamic()
			// will release it, if it really isn't needed anymore
			previous->Acquire();
		}
	}

	return _RegisterDynamic(previous);
}


status_t
device_node::Probe(const char* devicePath, uint32 updateCycle)
{
	if ((fFlags & NODE_FLAG_DEVICE_REMOVED) != 0
		|| updateCycle == fLastUpdateCycle)
		return B_OK;

	status_t status = InitDriver();
	if (status < B_OK)
		return status;

	MethodDeleter<device_node, bool, &device_node::UninitDriver> uninit(this);

	if ((fFlags & B_FIND_CHILD_ON_DEMAND) != 0) {
		bool matches = false;
		uint16 type = 0;
		uint16 subType = 0;
		if (get_attr_uint16(this, B_DEVICE_SUB_TYPE, &subType, false) == B_OK
			&& get_attr_uint16(this, B_DEVICE_TYPE, &type, false) == B_OK) {
			// Check if this node matches the device path
			// TODO: maybe make this extendible via settings file?
			if (!strcmp(devicePath, "disk")) {
				matches = type == PCI_mass_storage
					|| (type == PCI_base_peripheral
						&& (subType == PCI_sd_host
							|| subType == PCI_system_peripheral_other));
			} else if (!strcmp(devicePath, "audio")) {
				matches = type == PCI_multimedia
					&& (subType == PCI_audio || subType == PCI_hd_audio);
			} else if (!strcmp(devicePath, "net")) {
				matches = type == PCI_network;
			} else if (!strcmp(devicePath, "graphics")) {
				matches = type == PCI_display;
			} else if (!strcmp(devicePath, "video")) {
				matches = type == PCI_multimedia && subType == PCI_video;
			} else if (!strcmp(devicePath, "power")) {
				matches = type == PCI_data_acquisition;
			} else if (!strcmp(devicePath, "input")) {
				matches = type == PCI_data_acquisition
					&& subType == PCI_data_acquisition_other;
			}
		} else {
			// This driver does not support types, but still wants to its
			// children explored on demand only.
			matches = true;
			sGenericContextPath = devicePath;
		}

		if (matches) {
			fLastUpdateCycle = updateCycle;
				// This node will be probed in this update cycle

			status = _Probe();

			sGenericContextPath = NULL;
			return status;
		}

		return B_OK;
	}

	NodeList::Iterator iterator = fChildren.GetIterator();
	while (iterator.HasNext()) {
		device_node* child = iterator.Next();

		status = child->Probe(devicePath, updateCycle);
		if (status != B_OK)
			return status;
	}

	return B_OK;
}


status_t
device_node::Reprobe()
{
	status_t status = InitDriver();
	if (status < B_OK)
		return status;

	MethodDeleter<device_node, bool, &device_node::UninitDriver> uninit(this);

	// If this child has been probed already, probe it again
	status = _Probe();
	if (status != B_OK)
		return status;

	NodeList::Iterator iterator = fChildren.GetIterator();
	while (iterator.HasNext()) {
		device_node* child = iterator.Next();

		status = child->Reprobe();
		if (status != B_OK)
			return status;
	}

	return B_OK;
}


status_t
device_node::Rescan()
{
	status_t status = InitDriver();
	if (status < B_OK)
		return status;

	MethodDeleter<device_node, bool, &device_node::UninitDriver> uninit(this);

	if (DriverModule()->rescan_child_devices != NULL) {
		status = DriverModule()->rescan_child_devices(DriverData());
		if (status != B_OK)
			return status;
	}

	NodeList::Iterator iterator = fChildren.GetIterator();
	while (iterator.HasNext()) {
		device_node* child = iterator.Next();

		status = child->Rescan();
		if (status != B_OK)
			return status;
	}

	return B_OK;
}


/*!	Uninitializes all temporary references to the driver. The registration
	process keeps the driver initialized to optimize the startup procedure;
	this function gives this reference away again.
*/
void
device_node::UninitUnusedDriver()
{
	// First, we need to go to the leaf, and go back from there

	NodeList::Iterator iterator = fChildren.GetIterator();
	while (iterator.HasNext()) {
		device_node* child = iterator.Next();

		child->UninitUnusedDriver();
	}

	if (!IsInitialized()
		|| (fFlags & NODE_FLAG_REGISTER_INITIALIZED) == 0)
		return;

	fFlags &= ~NODE_FLAG_REGISTER_INITIALIZED;

	UninitDriver();
}


/*!	Calls device_removed() on this node and all of its children - starting
	with the deepest and last child.
	It will also remove the one reference that every node gets on its creation.
*/
void
device_node::DeviceRemoved()
{
	// notify children
	NodeList::ConstIterator iterator = Children().GetIterator();
	while (iterator.HasNext()) {
		device_node* child = iterator.Next();

		child->DeviceRemoved();
	}

	// notify devices
	DeviceList::ConstIterator deviceIterator = Devices().GetIterator();
	while (deviceIterator.HasNext()) {
		Device* device = deviceIterator.Next();

		if (device->Module() != NULL
			&& device->Module()->device_removed != NULL)
			device->Module()->device_removed(device->Data());
	}

	fFlags |= NODE_FLAG_DEVICE_REMOVED;

	if (IsInitialized() && DriverModule()->device_removed != NULL)
		DriverModule()->device_removed(this);

	if ((fFlags & B_KEEP_DRIVER_LOADED) != 0) {
		// There is no point in keeping this driver loaded when its device
		// is gone
		UninitDriver();
	}

	UninitUnusedDriver();
	Release();
}


void
device_node::Acquire()
{
	atomic_add(&fRefCount, 1);
}


bool
device_node::Release()
{
	if (atomic_add(&fRefCount, -1) > 1)
		return false;

	delete this;
	return true;
}


void
device_node::AddDevice(Device* device)
{
	fDevices.Add(device);
}


void
device_node::RemoveDevice(Device* device)
{
	char attrName[256];
	device_attr_private* attr;

	sprintf(attrName, "dev/%" B_PRIdINO "/path", device->ID());
	attr = find_attr(this, attrName, false, B_STRING_TYPE);
	if (attr != NULL) {
		fAttributes.Remove(attr);
		delete attr;
	}

	sprintf(attrName, "dev/%" B_PRIdINO "/driver", device->ID());
	attr = find_attr(this, attrName, false, B_STRING_TYPE);
	if (attr != NULL) {
		fAttributes.Remove(attr);
		delete attr;
	}

	fDevices.Remove(device);
}


int
device_node::CompareTo(const device_attr* attributes) const
{
	if (attributes == NULL)
		return -1;

	for (; attributes->name != NULL; attributes++) {
		// find corresponding attribute
		AttributeList::ConstIterator iterator = Attributes().GetIterator();
		device_attr_private* attr = NULL;
		bool found = false;

		while (iterator.HasNext()) {
			attr = iterator.Next();

			if (!strcmp(attr->name, attributes->name)) {
				found = true;
				break;
			}
		}
		if (!found)
			return -1;

		int compare = device_attr_private::Compare(attr, attributes);
		if (compare != 0)
			return compare;
	}

	return 0;
}


device_node*
device_node::FindChild(const device_attr* attributes) const
{
	if (attributes == NULL)
		return NULL;

	NodeList::ConstIterator iterator = Children().GetIterator();
	while (iterator.HasNext()) {
		device_node* child = iterator.Next();

		// ignore nodes that are pending to be removed
		if ((child->Flags() & NODE_FLAG_DEVICE_REMOVED) == 0
			&& !child->CompareTo(attributes))
			return child;
	}

	return NULL;
}


device_node*
device_node::FindChild(const char* moduleName) const
{
	if (moduleName == NULL)
		return NULL;

	NodeList::ConstIterator iterator = Children().GetIterator();
	while (iterator.HasNext()) {
		device_node* child = iterator.Next();

		if (!strcmp(child->ModuleName(), moduleName))
			return child;
	}

	return NULL;
}


/*!	This returns the priority or importance of this node. Nodes with higher
	priority are registered/probed first.
	Currently, only the B_FIND_MULTIPLE_CHILDREN flag alters the priority;
	it might make sense to be able to directly set the priority via an
	attribute.
*/
int32
device_node::Priority()
{
	return (fFlags & B_FIND_MULTIPLE_CHILDREN) != 0 ? 0 : 100;
}


void
device_node::Dump(int32 level)
{
	put_level(level);
	kprintf("(%" B_PRId32 ") @%p \"%s\" (ref %" B_PRId32 ", init %" B_PRId32
		", module %p, data %p)\n", level, this, ModuleName(), fRefCount,
		fInitialized, DriverModule(), DriverData());

	AttributeList::Iterator attribute = Attributes().GetIterator();
	while (attribute.HasNext()) {
		dump_attribute(attribute.Next(), level);
	}

	DeviceList::Iterator deviceIterator = fDevices.GetIterator();
	while (deviceIterator.HasNext()) {
		Device* device = deviceIterator.Next();
		put_level(level);
		kprintf("device: %s, %p\n", device->ModuleName(), device->Data());
	}

	NodeList::ConstIterator iterator = Children().GetIterator();
	while (iterator.HasNext()) {
		iterator.Next()->Dump(level + 1);
	}
}


//	#pragma mark - root node


static void
init_node_tree(void)
{
	device_attr attrs[] = {
		{B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {.string = "Devices Root"}},
		{B_DEVICE_BUS, B_STRING_TYPE, {.string = "root"}},
		{B_DEVICE_FLAGS, B_UINT32_TYPE,
			{.ui32 = B_FIND_MULTIPLE_CHILDREN | B_KEEP_DRIVER_LOADED }},
		{NULL}
	};

	device_node* node = NULL;
	if (register_node(NULL, DEVICE_MANAGER_ROOT_NAME, attrs, NULL, &node)
			!= B_OK) {
		dprintf("Cannot register Devices Root Node\n");
	}

	device_attr genericAttrs[] = {
		{B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {.string = "Generic"}},
		{B_DEVICE_BUS, B_STRING_TYPE, {.string = "generic"}},
		{B_DEVICE_FLAGS, B_UINT32_TYPE, {.ui32 = B_FIND_MULTIPLE_CHILDREN
			| B_KEEP_DRIVER_LOADED | B_FIND_CHILD_ON_DEMAND}},
		{NULL}
	};

	if (register_node(node, DEVICE_MANAGER_GENERIC_NAME, genericAttrs, NULL,
			NULL) != B_OK) {
		dprintf("Cannot register Generic Devices Node\n");
	}
}


driver_module_info gDeviceRootModule = {
	{
		DEVICE_MANAGER_ROOT_NAME,
		0,
		NULL,
	},
};


driver_module_info gDeviceGenericModule = {
	{
		DEVICE_MANAGER_GENERIC_NAME,
		0,
		NULL,
	},
	NULL
};


//	#pragma mark - private kernel API


status_t
device_manager_probe(const char* path, uint32 updateCycle)
{
	TRACE(("device_manager_probe(\"%s\")\n", path));
	RecursiveLocker _(sLock);

	// first, publish directories in the driver directory
	publish_directories(path);

	return sRootNode->Probe(path, updateCycle);
}


status_t
device_manager_init(struct kernel_args* args)
{
	TRACE(("device manager init\n"));

	IOSchedulerRoster::Init();

	dm_init_id_generator();
	dm_init_io_resources();

	recursive_lock_init(&sLock, "device manager");

	register_generic_syscall(DEVICE_MANAGER_SYSCALLS, control_device_manager,
		1, 0);

	add_debugger_command("dm_tree", &dump_device_nodes,
		"dump device node tree");

	init_node_tree();

	return B_OK;
}


status_t
device_manager_init_post_modules(struct kernel_args* args)
{
	RecursiveLocker _(sLock);
	return sRootNode->Reprobe();
}


recursive_lock*
device_manager_get_lock()
{
	return &sLock;
}