⛏️ index : haiku.git

/*
 * Copyright 2003-2008, Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Michael Lotz <mmlr@mlotz.ch>
 *		Niels S. Reedijk
 */


#include <module.h>
#include <unistd.h>
#include <util/kernel_cpp.h>
#include <util/AutoLock.h>

#include "usb_private.h"
#include "PhysicalMemoryAllocator.h"

#include <fs/devfs.h>
#include <kdevice_manager.h>


Stack::Stack()
	:	fExploreThread(-1),
		fExploreSem(-1),
		fAllocator(NULL),
		fObjectIndex(1),
		fObjectMaxCount(1024),
		fObjectArray(NULL),
		fDriverList(NULL)
{
	TRACE("stack init\n");

	mutex_init(&fStackLock, "usb stack lock");
	mutex_init(&fExploreLock, "usb explore lock");
	fExploreSem = create_sem(0, "usb explore sem");
	if (fExploreSem < B_OK) {
		TRACE_ERROR("failed to create semaphore\n");
		return;
	}

	size_t objectArraySize = fObjectMaxCount * sizeof(Object *);
	fObjectArray = (Object **)malloc(objectArraySize);
	if (fObjectArray == NULL) {
		TRACE_ERROR("failed to allocate object array\n");
		return;
	}

	memset(fObjectArray, 0, objectArraySize);

	fAllocator = new(std::nothrow) PhysicalMemoryAllocator("USB Stack Allocator",
		8, B_PAGE_SIZE * 32, 64);
	if (!fAllocator || fAllocator->InitCheck() < B_OK) {
		TRACE_ERROR("failed to allocate the allocator\n");
		delete fAllocator;
		fAllocator = NULL;
		return;
	}

	fExploreThread = spawn_kernel_thread(ExploreThread, "usb explore",
		B_LOW_PRIORITY, this);
	resume_thread(fExploreThread);
}


Stack::~Stack()
{
	int32 result;
	delete_sem(fExploreSem);
	fExploreSem = -1;
	wait_for_thread(fExploreThread, &result);

	mutex_lock(&fStackLock);
	mutex_destroy(&fStackLock);
	mutex_lock(&fExploreLock);
	mutex_destroy(&fExploreLock);

	// Release the bus modules
	for (Vector<BusManager *>::Iterator i = fBusManagers.Begin();
		i != fBusManagers.End(); i++) {
		delete (*i);
	}

	delete fAllocator;
	free(fObjectArray);
}


status_t
Stack::InitCheck()
{
	return (fExploreThread >= 0) ? B_OK : B_NO_INIT;
}


bool
Stack::Lock()
{
	return (mutex_lock(&fStackLock) == B_OK);
}


void
Stack::Unlock()
{
	mutex_unlock(&fStackLock);
}


usb_id
Stack::GetUSBID(Object *object)
{
	if (!Lock())
		return fObjectMaxCount;

	uint32 id = fObjectIndex;
	uint32 tries = fObjectMaxCount;
	while (tries-- > 0) {
		if (fObjectArray[id] == NULL) {
			fObjectIndex = (id + 1) % fObjectMaxCount;
			fObjectArray[id] = object;
			Unlock();
			return (usb_id)id;
		}

		id = (id + 1) % fObjectMaxCount;
	}

	TRACE_ERROR("the stack has run out of usb_ids\n");
	Unlock();
	return 0;
}


void
Stack::PutUSBID(Object *object)
{
	if (!Lock())
		return;

	usb_id id = object->USBID();
	if (id >= fObjectMaxCount) {
		TRACE_ERROR("tried to put an invalid usb_id\n");
		Unlock();
		return;
	}
	if (fObjectArray[id] != object) {
		TRACE_ERROR("tried to put an object with incorrect usb_id\n");
		Unlock();
		return;
	}

	fObjectArray[id] = NULL;

#if KDEBUG
	// Validate that no children of this object are still in the stack.
	for (usb_id i = 0; i < fObjectMaxCount; i++) {
		if (fObjectArray[i] == NULL)
			continue;

		ASSERT_PRINT(fObjectArray[i]->Parent() != object,
			"%s", fObjectArray[i]->TypeName());
	}
#endif

	Unlock();
}


Object *
Stack::GetObject(usb_id id)
{
	if (!Lock())
		return NULL;

	if (id >= fObjectMaxCount) {
		TRACE_ERROR("tried to get object with invalid usb_id\n");
		Unlock();
		return NULL;
	}

	Object *result = fObjectArray[id];

	if (result != NULL)
		result->AcquireReference();

	Unlock();
	return result;
}


Object *
Stack::GetObjectNoLock(usb_id id) const
{
	ASSERT(debug_debugger_running());
	if (id >= fObjectMaxCount)
		return NULL;
	return fObjectArray[id];
}


int32
Stack::ExploreThread(void *data)
{
	Stack *stack = (Stack *)data;

	while (acquire_sem_etc(stack->fExploreSem, 1, B_RELATIVE_TIMEOUT,
			USB_DELAY_HUB_EXPLORE) != B_BAD_SEM_ID) {
		stack->Explore();
	}

	return B_OK;
}


void
Stack::Explore()
{
	recursive_lock* dmLock = device_manager_get_lock();
	if (find_thread(NULL) != fExploreThread
			&& RECURSIVE_LOCK_HOLDER(dmLock) == find_thread(NULL)) {
		// This should only happen during the initial device scan, during which
		// we should be able to acquire the explore lock immediately (since the
		// explore thread will be waiting on the device manager lock as below),
		// but in case we aren't, use a timeout to avoid lock-order-inversion deadlocks.
		if (mutex_lock_with_timeout(&fExploreLock, B_RELATIVE_TIMEOUT, 1000) != B_OK) {
			release_sem(fExploreSem);
			return;
		}
	} else {
		// Temporarily acquire the device manager lock, to ensure it isn't scanning.
		RecursiveLocker dmLocker(dmLock);

		if (mutex_lock(&fExploreLock) != B_OK)
			return;

		dmLocker.Unlock();
	}

	int32 semCount = 0;
	get_sem_count(fExploreSem, &semCount);
	if (semCount > 0)
		acquire_sem_etc(fExploreSem, semCount, B_RELATIVE_TIMEOUT, 0);

	rescan_item *rescanList = NULL;
	change_item *changeItem = NULL;
	for (int32 i = 0; i < fBusManagers.Count(); i++) {
		Hub *rootHub = fBusManagers.ElementAt(i)->GetRootHub();
		if (rootHub)
			rootHub->Explore(&changeItem);
	}

	while (changeItem) {
		NotifyDeviceChange(changeItem->device, &rescanList, changeItem->added);
		if (!changeItem->added) {
			// everyone possibly holding a reference is now notified so we
			// can delete the device
			changeItem->device->GetBusManager()->FreeDevice(changeItem->device);
		}

		change_item *next = changeItem->link;
		delete changeItem;
		changeItem = next;
	}

	mutex_unlock(&fExploreLock);
	RescanDrivers(rescanList);
}

void
Stack::AddBusManager(BusManager *busManager)
{
	MutexLocker _(fExploreLock);
	fBusManagers.PushBack(busManager);
}


int32
Stack::IndexOfBusManager(BusManager *busManager) const
{
	return fBusManagers.IndexOf(busManager);
}


BusManager *
Stack::BusManagerAt(int32 index) const
{
	return fBusManagers.ElementAt(index);
}


status_t
Stack::AllocateChunk(void **logicalAddress, phys_addr_t *physicalAddress,
	size_t size)
{
	return fAllocator->Allocate(size, logicalAddress, physicalAddress);
}


status_t
Stack::FreeChunk(void *logicalAddress, phys_addr_t physicalAddress,
	size_t size)
{
	return fAllocator->Deallocate(size, logicalAddress, physicalAddress);
}


area_id
Stack::AllocateArea(void **logicalAddress, phys_addr_t *physicalAddress, size_t size,
	const char *name)
{
	TRACE("allocating %ld bytes for %s\n", size, name);

	void *logAddress;
	size = (size + B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1);
	area_id area = create_area(name, &logAddress, B_ANY_KERNEL_ADDRESS, size,
		B_32_BIT_CONTIGUOUS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
		// TODO: Use B_CONTIGUOUS when the TODOs regarding 64 bit physical
		// addresses are fixed (if possible).

	if (area < B_OK) {
		TRACE_ERROR("couldn't allocate area %s\n", name);
		return B_ERROR;
	}

	physical_entry physicalEntry;
	status_t result = get_memory_map(logAddress, size, &physicalEntry, 1);
	if (result < B_OK) {
		delete_area(area);
		TRACE_ERROR("couldn't map area %s\n", name);
		return B_ERROR;
	}

	memset(logAddress, 0, size);
	if (logicalAddress)
		*logicalAddress = logAddress;

	if (physicalAddress)
		*physicalAddress = (phys_addr_t)physicalEntry.address;

	TRACE("area = %" B_PRId32 ", size = %" B_PRIuSIZE ", log = %p, phy = %#"
		B_PRIxPHYSADDR "\n", area, size, logAddress, physicalEntry.address);
	return area;
}


void
Stack::NotifyDeviceChange(Device *device, rescan_item **rescanList, bool added)
{
	TRACE("device %s\n", added ? "added" : "removed");

	usb_driver_info *element = fDriverList;
	while (element) {
		status_t result = device->ReportDevice(element->support_descriptors,
			element->support_descriptor_count, &element->notify_hooks,
			&element->cookies, added, false);

		if (result >= B_OK) {
			const char *driverName = element->driver_name;
			if (element->republish_driver_name)
				driverName = element->republish_driver_name;

			bool already = false;
			rescan_item *rescanItem = *rescanList;
			while (rescanItem) {
				if (strcmp(rescanItem->name, driverName) == 0) {
					// this driver is going to be rescanned already
					already = true;
					break;
				}
				rescanItem = rescanItem->link;
			}

			if (!already) {
				rescanItem = new(std::nothrow) rescan_item;
				if (!rescanItem)
					return;

				rescanItem->name = driverName;
				rescanItem->link = *rescanList;
				*rescanList = rescanItem;
			}
		}

		element = element->link;
	}
}


void
Stack::RescanDrivers(rescan_item *rescanItem)
{
	while (rescanItem) {
		// the device is supported by this driver. it either got notified
		// already by the hooks or it is not loaded at this time. in any
		// case we will rescan the driver so it either is loaded and can
		// scan for supported devices or its publish_devices hook will be
		// called to expose changed devices.

		// use the private devfs API to republish a device
		devfs_rescan_driver(rescanItem->name);

		rescan_item *next = rescanItem->link;
		delete rescanItem;
		rescanItem = next;
	}
}


status_t
Stack::RegisterDriver(const char *driverName,
	const usb_support_descriptor *descriptors,
	size_t descriptorCount, const char *republishDriverName)
{
	TRACE("register driver \"%s\"\n", driverName);
	if (!driverName)
		return B_BAD_VALUE;

	if (!Lock())
		return B_ERROR;

	usb_driver_info *element = fDriverList;
	while (element) {
		if (strcmp(element->driver_name, driverName) == 0) {
			// we already have an entry for this driver, just update it
			free((char *)element->republish_driver_name);
			element->republish_driver_name = strdup(republishDriverName);

			free(element->support_descriptors);
			size_t descriptorsSize = descriptorCount * sizeof(usb_support_descriptor);
			element->support_descriptors = (usb_support_descriptor *)malloc(descriptorsSize);
			memcpy(element->support_descriptors, descriptors, descriptorsSize);
			element->support_descriptor_count = descriptorCount;

			Unlock();
			return B_OK;
		}

		element = element->link;
	}

	// this is a new driver, add it to the driver list
	usb_driver_info *info = new(std::nothrow) usb_driver_info;
	if (!info) {
		Unlock();
		return B_NO_MEMORY;
	}

	info->driver_name = strdup(driverName);
	info->republish_driver_name = strdup(republishDriverName);

	size_t descriptorsSize = descriptorCount * sizeof(usb_support_descriptor);
	info->support_descriptors = (usb_support_descriptor *)malloc(descriptorsSize);
	memcpy(info->support_descriptors, descriptors, descriptorsSize);
	info->support_descriptor_count = descriptorCount;

	info->notify_hooks.device_added = NULL;
	info->notify_hooks.device_removed = NULL;
	info->cookies = NULL;
	info->link = NULL;

	if (fDriverList) {
		usb_driver_info *element = fDriverList;
		while (element->link)
			element = element->link;

		element->link = info;
	} else
		fDriverList = info;

	Unlock();
	return B_OK;
}


status_t
Stack::InstallNotify(const char *driverName, const usb_notify_hooks *hooks)
{
	TRACE("installing notify hooks for driver \"%s\"\n", driverName);

	usb_driver_info *element = fDriverList;
	while (element) {
		if (strcmp(element->driver_name, driverName) == 0) {
			if (mutex_lock(&fExploreLock) != B_OK)
				return B_ERROR;

			// inform driver about any already present devices
			for (int32 i = 0; i < fBusManagers.Count(); i++) {
				Hub *rootHub = fBusManagers.ElementAt(i)->GetRootHub();
				if (rootHub) {
					// Report device will recurse down the whole tree
					rootHub->ReportDevice(element->support_descriptors,
						element->support_descriptor_count, hooks,
						&element->cookies, true, true);
				}
			}

			element->notify_hooks.device_added = hooks->device_added;
			element->notify_hooks.device_removed = hooks->device_removed;
			mutex_unlock(&fExploreLock);
			return B_OK;
		}

		element = element->link;
	}

	return B_NAME_NOT_FOUND;
}


status_t
Stack::UninstallNotify(const char *driverName)
{
	TRACE("uninstalling notify hooks for driver \"%s\"\n", driverName);

	usb_driver_info *element = fDriverList;
	while (element) {
		if (strcmp(element->driver_name, driverName) == 0) {
			if (mutex_lock(&fExploreLock) != B_OK)
				return B_ERROR;

			// trigger the device removed hook
			for (int32 i = 0; i < fBusManagers.Count(); i++) {
				Hub *rootHub = fBusManagers.ElementAt(i)->GetRootHub();
				if (rootHub)
					rootHub->ReportDevice(element->support_descriptors,
						element->support_descriptor_count,
						&element->notify_hooks, &element->cookies, false, true);
			}

			element->notify_hooks.device_added = NULL;
			element->notify_hooks.device_removed = NULL;
			mutex_unlock(&fExploreLock);
			return B_OK;
		}

		element = element->link;
	}

	return B_NAME_NOT_FOUND;
}